From e11d7d1c649c9a7230bce622d234f84cd51fa746 Mon Sep 17 00:00:00 2001
From: Jonathan Hoffstadt
Date: Thu, 9 Jan 2025 21:30:50 -0600
Subject: [PATCH 01/58] chore: Happy New Year!
---
LICENSE | 2 +-
docs/source/conf.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/LICENSE b/LICENSE
index e382656bb..15b4874d5 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024 Dear PyGui, LLC
+Copyright (c) 2025 Dear PyGui, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/docs/source/conf.py b/docs/source/conf.py
index aebf6928a..15260b499 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -16,7 +16,7 @@
# -- Project information -----------------------------------------------------
project = 'Dear PyGui'
-copyright = '2024, Jonathan Hoffstadt and Preston Cothren'
+copyright = '2025, Jonathan Hoffstadt and Preston Cothren'
author = 'Jonathan Hoffstadt and Preston Cothren'
From f6861b317d318da7ffcc8c45b50e7beda187c09d Mon Sep 17 00:00:00 2001
From: Vladimir Ein
Date: Thu, 13 Feb 2025 23:49:09 +0500
Subject: [PATCH 02/58] fix (tables): Table columns become hidden in an empty
table #2449 (#2475)
---
src/mvTables.cpp | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/mvTables.cpp b/src/mvTables.cpp
index 21c2cda91..0b09b9d1f 100644
--- a/src/mvTables.cpp
+++ b/src/mvTables.cpp
@@ -362,13 +362,17 @@ void mvTable::draw(ImDrawList* drawlist, float x, float y)
// columns
int columnnum = 0;
+ int last_row = ImGui::TableGetRowIndex();
for (auto& item : childslots[0])
{
ImGuiTableColumnFlags flags = ImGui::TableGetColumnFlags(columnnum);
item->state.lastFrameUpdate = GContext->frame;
item->state.visible = flags & ImGuiTableColumnFlags_IsVisible;
item->state.hovered = flags & ImGuiTableColumnFlags_IsHovered;
- if (item->config.enabled)
+ // Note: when the table is empty, TableGetColumnFlags will incorrectly return
+ // zero status flags for all columns. While this is fine for `visible` and `hovered`,
+ // we definitely don't want to push that zero into `show`.
+ if (item->config.enabled && last_row >= 0)
{
// Sync the flag with the actual column state controlled by the
// user via context menu.
From c1d9acf3648b0a53341afd9fb44366aa1f2f2e69 Mon Sep 17 00:00:00 2001
From: Samuele Mazzi
Date: Thu, 13 Feb 2025 19:50:15 +0100
Subject: [PATCH 03/58] feat: add missing `NumPad` keys (#2468)
---
dearpygui/_dearpygui.pyi | 2 ++
dearpygui/_dearpygui_RTD.py | 2 ++
dearpygui/dearpygui.py | 2 ++
src/mvContext.cpp | 2 ++
4 files changed, 8 insertions(+)
diff --git a/dearpygui/_dearpygui.pyi b/dearpygui/_dearpygui.pyi
index b821d6276..3768e3f25 100644
--- a/dearpygui/_dearpygui.pyi
+++ b/dearpygui/_dearpygui.pyi
@@ -1331,6 +1331,8 @@ mvKey_NumPad6=0
mvKey_NumPad7=0
mvKey_NumPad8=0
mvKey_NumPad9=0
+mvKey_NumPadEnter=0
+mvKey_NumPadEqual=0
mvKey_Subtract=0
mvKey_Decimal=0
mvKey_Divide=0
diff --git a/dearpygui/_dearpygui_RTD.py b/dearpygui/_dearpygui_RTD.py
index be2649027..65f4f23e9 100644
--- a/dearpygui/_dearpygui_RTD.py
+++ b/dearpygui/_dearpygui_RTD.py
@@ -8884,6 +8884,8 @@ def unstage(item):
mvKey_NumPad7=internal_dpg.mvKey_NumPad7
mvKey_NumPad8=internal_dpg.mvKey_NumPad8
mvKey_NumPad9=internal_dpg.mvKey_NumPad9
+mvKey_NumPadEnter=internal_dpg.mvKey_NumPadEnter
+mvKey_NumPadEqual=internal_dpg.mvKey_NumPadEqual
mvKey_Subtract=internal_dpg.mvKey_Subtract
mvKey_Decimal=internal_dpg.mvKey_Decimal
mvKey_Divide=internal_dpg.mvKey_Divide
diff --git a/dearpygui/dearpygui.py b/dearpygui/dearpygui.py
index 3fecfaa12..6c69a5027 100644
--- a/dearpygui/dearpygui.py
+++ b/dearpygui/dearpygui.py
@@ -9857,6 +9857,8 @@ def unstage(item : Union[int, str], **kwargs) -> None:
mvKey_NumPad7=internal_dpg.mvKey_NumPad7
mvKey_NumPad8=internal_dpg.mvKey_NumPad8
mvKey_NumPad9=internal_dpg.mvKey_NumPad9
+mvKey_NumPadEnter=internal_dpg.mvKey_NumPadEnter
+mvKey_NumPadEqual=internal_dpg.mvKey_NumPadEqual
mvKey_Subtract=internal_dpg.mvKey_Subtract
mvKey_Decimal=internal_dpg.mvKey_Decimal
mvKey_Divide=internal_dpg.mvKey_Divide
diff --git a/src/mvContext.cpp b/src/mvContext.cpp
index db10dd2c2..00200ca9f 100644
--- a/src/mvContext.cpp
+++ b/src/mvContext.cpp
@@ -298,6 +298,8 @@ InsertConstants_mvContext(std::vector>& constants)
constants.emplace_back("mvKey_NumPad7", ImGuiKey_Keypad7);
constants.emplace_back("mvKey_NumPad8", ImGuiKey_Keypad8);
constants.emplace_back("mvKey_NumPad9", ImGuiKey_Keypad9);
+ constants.emplace_back("mvKey_NumPadEnter", ImGuiKey_KeypadEnter);
+ constants.emplace_back("mvKey_NumPadEqual", ImGuiKey_KeypadEqual);
constants.emplace_back("mvKey_Subtract", ImGuiKey_KeypadSubtract);
constants.emplace_back("mvKey_Decimal", ImGuiKey_KeypadDecimal);
constants.emplace_back("mvKey_Divide", ImGuiKey_KeypadDivide);
From 2b171ca13e2496a6b3b78cb166ef32eb2f6319f0 Mon Sep 17 00:00:00 2001
From: Vladimir Ein
Date: Fri, 14 Feb 2025 07:54:27 +0500
Subject: [PATCH 04/58] fix: Removed useless lookups in move_item #2343 (#2476)
---
src/mvItemRegistry.cpp | 123 +++++++++++++----------------------------
1 file changed, 39 insertions(+), 84 deletions(-)
diff --git a/src/mvItemRegistry.cpp b/src/mvItemRegistry.cpp
index ceecd0412..442c27423 100644
--- a/src/mvItemRegistry.cpp
+++ b/src/mvItemRegistry.cpp
@@ -386,115 +386,68 @@ MoveDownRoot(std::vector>& roots, mvUUID uuid)
static b8
AddRuntimeChild(mvAppItem* rootitem, mvUUID parent, mvUUID before, std::shared_ptr item)
{
- if (before == 0 && parent == 0)
- return false;
-
- for (auto& children : rootitem->childslots)
+ //this is the container, add item to end.
+ if (before == 0)
{
- //this is the container, add item to end.
- if (before == 0)
+ // checking if `rootitem` is the parent we're looking for
+ if (rootitem->uuid == parent)
{
-
- if (rootitem->uuid == parent)
- {
- i32 targetSlot = DearPyGui::GetEntityTargetSlot(item->type);
- item->info.location = (i32)rootitem->childslots[targetSlot].size();
- rootitem->childslots[targetSlot].push_back(item);
- DearPyGui::OnChildAdded(rootitem, item);
- item->info.parentPtr = rootitem;
- item->config.parent = rootitem->uuid;
- return true;
- }
-
- // check children
- for (auto& childslot : rootitem->childslots)
- {
- for (auto& child : childslot)
- {
-
- if (!child)
- continue;
-
- if (DearPyGui::GetEntityDesciptionFlags(child->type) & MV_ITEM_DESC_CONTAINER
- || DearPyGui::GetEntityDesciptionFlags(item->type) & MV_ITEM_DESC_HANDLER)
- {
- // parent found
- if (AddRuntimeChild(child.get(), parent, before, item))
- return true;
- }
- }
- }
+ i32 targetSlot = DearPyGui::GetEntityTargetSlot(item->type);
+ item->info.location = (i32)rootitem->childslots[targetSlot].size();
+ rootitem->childslots[targetSlot].push_back(item);
+ item->info.parentPtr = rootitem;
+ item->config.parent = rootitem->uuid;
+ DearPyGui::OnChildAdded(rootitem, item);
+ return true;
}
- // this is the container, add item to beginning.
- else
+ }
+ // this is the container, add item to beginning.
+ else
+ {
+ for (auto& childslot : rootitem->childslots)
{
- bool beforeFound = false;
-
- // check children
- for (auto& child : children)
+ for (auto it = childslot.begin(); it != childslot.end(); ++it)
+ // for (auto& child : childslot)
{
+ auto child = *it;
if (!child)
continue;
if (child->uuid == before)
{
- beforeFound = true;
- break;
- }
- }
-
-
- // after item is in this container
- if (beforeFound)
- {
- item->info.parentPtr = rootitem;
-
- std::vector> oldchildren = children;
- children.clear();
-
- int location = 0;
- for (auto& child : oldchildren)
- {
- if (!child)
- continue;
-
- if (child->uuid == before)
- {
- children.push_back(item);
- item->info.location = location;
- DearPyGui::OnChildAdded(rootitem, item);
- ++location;
- }
- children.push_back(child);
- ++location;
-
+ childslot.insert(it, item);
+ item->info.parentPtr = rootitem;
+ item->config.parent = rootitem->uuid;
+ // TODO: this one can be optimized a lot (we only need to update
+ // locations in the tail of the current slot, not in all 4 slots).
+ UpdateChildLocations(rootitem->childslots, 4);
+ DearPyGui::OnChildAdded(rootitem, item);
+ return true;
}
-
- // TODO: maybe remove this call since location gets updated inside the loop
- UpdateChildLocations(rootitem->childslots, 4);
-
- return true;
}
}
+ }
- // check children
- for (auto& child : children)
+ bool is_handler = (DearPyGui::GetEntityDesciptionFlags(item->type) & MV_ITEM_DESC_HANDLER);
+
+ // check children
+ for (auto& childslot : rootitem->childslots)
+ {
+ for (auto& child : childslot)
{
+
if (!child)
continue;
- if (DearPyGui::GetEntityDesciptionFlags(child->type) & MV_ITEM_DESC_CONTAINER
- || DearPyGui::GetEntityDesciptionFlags(item->type) & MV_ITEM_DESC_HANDLER)
+ if (DearPyGui::GetEntityDesciptionFlags(child->type) & MV_ITEM_DESC_CONTAINER || is_handler)
{
// parent found
if (AddRuntimeChild(child.get(), parent, before, item))
return true;
}
}
-
- };
-
+ }
return false;
}
@@ -623,6 +576,8 @@ AddItem(mvItemRegistry& registry, std::shared_ptr item)
static b8
AddRuntimeItem(mvItemRegistry& registry, mvUUID parent, mvUUID before, std::shared_ptr item)
{
+ if (before == 0 && parent == 0)
+ return false;
if (AddRuntimeChildRoot(registry.colormapRoots, parent, before, item)) return true;
else if (AddRuntimeChildRoot(registry.filedialogRoots, parent, before, item)) return true;
From 4eae7e22bf9b5b87accb410178cd46d71ae99617 Mon Sep 17 00:00:00 2001
From: Vladimir Ein
Date: Fri, 28 Feb 2025 19:28:26 +0500
Subject: [PATCH 05/58] fix (mvSpacer): Ability to hide spacers (#2474)
---
src/mvBasicWidgets.cpp | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/mvBasicWidgets.cpp b/src/mvBasicWidgets.cpp
index ab9ef1d9c..f8c48244c 100644
--- a/src/mvBasicWidgets.cpp
+++ b/src/mvBasicWidgets.cpp
@@ -6349,6 +6349,8 @@ DearPyGui::draw_separator(ImDrawList* drawlist, mvAppItem& item)
void
DearPyGui::draw_spacer(ImDrawList* drawlist, mvAppItem& item)
{
+ if (!item.config.show)
+ return;
if (item.config.width == 0 && item.config.height == 0)
ImGui::Spacing();
else
From 5d471f6b405d4c096048314ed2a27b3cddb729b7 Mon Sep 17 00:00:00 2001
From: Vladimir Ein
Date: Thu, 29 Dec 2022 18:48:35 +0500
Subject: [PATCH 06/58] fix: tables with zero columns break theme/font stack
---
src/mvTables.cpp | 35 ++++++++++++++++-------------------
1 file changed, 16 insertions(+), 19 deletions(-)
diff --git a/src/mvTables.cpp b/src/mvTables.cpp
index 0b09b9d1f..ab2aa4cb6 100644
--- a/src/mvTables.cpp
+++ b/src/mvTables.cpp
@@ -136,6 +136,22 @@ void mvTable::draw(ImDrawList* drawlist, float x, float y)
if (!config.show)
return;
+ // Validating column visibility: if there are no any visible/enabled columns,
+ // an attempt to render the table will corrupt data structures within ImGui,
+ // and will most probably lead to a crash on app shutdown. On the other hand,
+ // if there are no visible columns, we don't have anything to draw, really.
+ bool all_hidden = true;
+ for (auto& item : childslots[0])
+ {
+ if (item->config.enabled && item->config.show)
+ {
+ all_hidden = false;
+ break;
+ }
+ }
+ if (all_hidden)
+ return;
+
// push font if a font object is attached
if (font)
{
@@ -149,25 +165,6 @@ void mvTable::draw(ImDrawList* drawlist, float x, float y)
{
ScopedID id(uuid);
- if (_columns == 0)
- return;
-
- // Validating column visibility: if there are no any visible/enabled columns,
- // an attempt to render the table will corrupt data structures within ImGui,
- // and will most probably lead to a crash on app shutdown. On the other hand,
- // if there are no visible columns, we don't have anything to draw, really.
- bool all_hidden = true;
- for (auto& item : childslots[0])
- {
- if (item->config.enabled && item->config.show)
- {
- all_hidden = false;
- break;
- }
- }
- if (all_hidden)
- return;
-
auto row_renderer = [&](mvAppItem* row, mvAppItem* prev_visible_row=nullptr)
{
//TableNextRow() ends the previous row, if any, and determines background color for it.
From c6282f860bc01ba27741e4b71dc332ad8e0bde3b Mon Sep 17 00:00:00 2001
From: Jonathan Hoffstadt
Date: Wed, 16 Apr 2025 20:14:31 -0500
Subject: [PATCH 07/58] chore: update code owners
---
.github/CODEOWNERS | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index cb564d3d6..194fcade9 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -4,8 +4,7 @@
# directories
/thirdparty/ @hoffstadt
/scripts/ @hoffstadt
-/src/ @hoffstadt @Pcothren
-/docs/* @hoffstadt @Pcothren
+/docs/* @hoffstadt
# specific files
/src/distribution.cmake @hoffstadt
@@ -18,7 +17,7 @@ setup.py @hoffstadt
.gitattributes @hoffstadt
.gitignore @hoffstadt
.gitmodules @hoffstadt
-.readthedocs.yaml @hoffstadt @Pcothren
+.readthedocs.yaml @hoffstadt
# no owners
README.md
From 23c68815a11607b7f09612728951cc88d996e53d Mon Sep 17 00:00:00 2001
From: Jonathan Hoffstadt
Date: Wed, 16 Apr 2025 20:38:26 -0500
Subject: [PATCH 08/58] ci: update ubuntu runners from 20.04 to 22.04
---
.github/workflows/Deployment.yml | 2 +-
.github/workflows/EmbeddedBuild.yml | 2 +-
.github/workflows/static-analysis.yml | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/Deployment.yml b/.github/workflows/Deployment.yml
index 89bc21bc7..088d782bf 100644
--- a/.github/workflows/Deployment.yml
+++ b/.github/workflows/Deployment.yml
@@ -156,7 +156,7 @@ jobs:
build-linux-wheels:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
env:
CXX: g++-9
strategy:
diff --git a/.github/workflows/EmbeddedBuild.yml b/.github/workflows/EmbeddedBuild.yml
index 6814927b4..090644547 100644
--- a/.github/workflows/EmbeddedBuild.yml
+++ b/.github/workflows/EmbeddedBuild.yml
@@ -97,7 +97,7 @@ jobs:
build-Ubuntu:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
env:
CXX: g++-9
if: ${{! contains(github.event.head_commit.message, '[individual]') || contains(github.event.head_commit.message, '[linux]')}}
diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml
index 750884461..12c507982 100644
--- a/.github/workflows/static-analysis.yml
+++ b/.github/workflows/static-analysis.yml
@@ -18,7 +18,7 @@ on:
jobs:
PVS-Studio:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
if: ${{! contains(github.event.head_commit.message, '[individual]') || contains(github.event.head_commit.message, '[static analysis]')}}
steps:
- uses: actions/checkout@v4
From 3498225e1726747bca9d382010e65c22eb691a07 Mon Sep 17 00:00:00 2001
From: Jonathan Hoffstadt
Date: Sun, 27 Apr 2025 21:18:08 -0500
Subject: [PATCH 09/58] ci: update deployment macos-12 to macos-13
---
.github/workflows/Deployment.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/Deployment.yml b/.github/workflows/Deployment.yml
index 088d782bf..6e4c11b72 100644
--- a/.github/workflows/Deployment.yml
+++ b/.github/workflows/Deployment.yml
@@ -74,7 +74,7 @@ jobs:
build-mac10-wheels:
- runs-on: macos-12
+ runs-on: macos-13
strategy:
matrix:
python-version: [ 3.8, 3.9, "3.10", "3.11", "3.12", "3.13" ]
@@ -238,4 +238,4 @@ jobs:
python -m twine upload --repository testpypi windowsbuild*/* -u __token__ -p ${{ secrets.TEST_PYPI_PASSWORD }} --skip-existing
python -m twine upload --repository testpypi apple10build*/* -u __token__ -p ${{ secrets.TEST_PYPI_PASSWORD }} --skip-existing
python -m twine upload --repository testpypi apple10sbuild*/* -u __token__ -p ${{ secrets.TEST_PYPI_PASSWORD }} --skip-existing
- python -m twine upload --repository testpypi linuxbuild*/* -u __token__ -p ${{ secrets.TEST_PYPI_PASSWORD }} --skip-existing
\ No newline at end of file
+ python -m twine upload --repository testpypi linuxbuild*/* -u __token__ -p ${{ secrets.TEST_PYPI_PASSWORD }} --skip-existing
From d3577817fa69d6b5a8b56fde023b0200c0ff83ef Mon Sep 17 00:00:00 2001
From: Jonathan Hoffstadt
Date: Sun, 27 Apr 2025 21:52:57 -0500
Subject: [PATCH 10/58] docs: update readme badge urls
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index e14a6dcbf..ff9e8c949 100644
--- a/README.md
+++ b/README.md
@@ -15,8 +15,8 @@
-
-
+
+
From 248fba81751a8ca298ac81290efefd7419763606 Mon Sep 17 00:00:00 2001
From: Jonathan Hoffstadt
Date: Fri, 30 May 2025 22:46:25 -0500
Subject: [PATCH 11/58] ci: update windows runners from 2019 to 2022
---
.github/workflows/Deployment.yml | 2 +-
.github/workflows/EmbeddedBuild.yml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/Deployment.yml b/.github/workflows/Deployment.yml
index 6e4c11b72..61ffda95c 100644
--- a/.github/workflows/Deployment.yml
+++ b/.github/workflows/Deployment.yml
@@ -30,7 +30,7 @@ jobs:
build-windows-wheels:
- runs-on: windows-2019
+ runs-on: windows-2022
strategy:
matrix:
python-version: [ 3.8, 3.9, "3.10", "3.11", "3.12", "3.13" ]
diff --git a/.github/workflows/EmbeddedBuild.yml b/.github/workflows/EmbeddedBuild.yml
index 090644547..08ca9bc55 100644
--- a/.github/workflows/EmbeddedBuild.yml
+++ b/.github/workflows/EmbeddedBuild.yml
@@ -25,7 +25,7 @@ jobs:
build-windows:
- runs-on: windows-2019
+ runs-on: windows-2022
if: ${{! contains(github.event.head_commit.message, '[individual]') || contains(github.event.head_commit.message, '[windows]')}}
steps:
From b0ba13d2c9e583f4da514271753458007d3de7b0 Mon Sep 17 00:00:00 2001
From: Jonathan Hoffstadt
Date: Tue, 10 Jun 2025 21:30:06 -0500
Subject: [PATCH 12/58] fix: visual studio 2022 builds
---
src/mvLoadingIndicatorCustom.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/mvLoadingIndicatorCustom.cpp b/src/mvLoadingIndicatorCustom.cpp
index c3ecbd420..e721db7dd 100644
--- a/src/mvLoadingIndicatorCustom.cpp
+++ b/src/mvLoadingIndicatorCustom.cpp
@@ -1,5 +1,6 @@
#include "mvLoadingIndicatorCustom.h"
#include
+#include
#include
// Posted by @alexsr here: https://github.com/ocornut/imgui/issues/1901
From 6400103d7679a4394fa0a8b17a8c0c7513271c34 Mon Sep 17 00:00:00 2001
From: Jonathan Hoffstadt
Date: Tue, 10 Jun 2025 21:55:07 -0500
Subject: [PATCH 13/58] ci: fix setup.py for visual studio 2019 deprecation
---
.github/workflows/Deployment.yml | 2 +-
setup.py | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/Deployment.yml b/.github/workflows/Deployment.yml
index 61ffda95c..8b6b695fc 100644
--- a/.github/workflows/Deployment.yml
+++ b/.github/workflows/Deployment.yml
@@ -202,7 +202,7 @@ jobs:
deploy-packages:
needs: [build-windows-wheels, build-mac10-wheels, build-linux-wheels, build-mac-silicon-wheels]
- runs-on: windows-2019
+ runs-on: windows-2022
steps:
diff --git a/setup.py b/setup.py
index 8350a9607..8198c8a48 100644
--- a/setup.py
+++ b/setup.py
@@ -65,10 +65,10 @@ def run(self):
return
if get_platform() == "Windows":
- command = [r'set PATH="C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin";"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin";"C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin";%PATH% && ']
+ command = [r'set PATH="C:\Program Files (x86)\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin";"C:\Program Files (x86)\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin";"C:\Program Files (x86)\Microsoft Visual Studio\2022\Professional\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin";%PATH% && ']
command.append("mkdir cmake-build-local && ")
command.append("cd cmake-build-local && ")
- command.append('cmake .. -G "Visual Studio 16 2019" -A "x64" -DMVDIST_ONLY=True -DMVDPG_VERSION=')
+ command.append('cmake .. -G "Visual Studio 17 2022" -A "x64" -DMVDIST_ONLY=True -DMVDPG_VERSION=')
command.append(version_number() + " -DMV_PY_VERSION=")
command.append(str(sys.version_info[0]) + "." + str(sys.version_info[1]) + " && ")
command.append("cd .. && cmake --build cmake-build-local --config Release")
From 51ab912c1ca493f67b48608d77157461897a851a Mon Sep 17 00:00:00 2001
From: Jonathan Hoffstadt
Date: Sun, 22 Jun 2025 20:08:01 -0500
Subject: [PATCH 14/58] ci: remove code owners
---
.github/CODEOWNERS | 2 --
1 file changed, 2 deletions(-)
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 194fcade9..b23dae7d8 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,5 +1,3 @@
-# default owners
-* @hoffstadt
# directories
/thirdparty/ @hoffstadt
From cd6dd42a7c10915c0769c575d0b6088821cd8124 Mon Sep 17 00:00:00 2001
From: ZhanYF <87886461+ZhanYF@users.noreply.github.com>
Date: Sun, 10 Nov 2024 10:02:12 +0000
Subject: [PATCH 15/58] Typo in demo.py
---
dearpygui/demo.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dearpygui/demo.py b/dearpygui/demo.py
index 08208442b..62b349501 100644
--- a/dearpygui/demo.py
+++ b/dearpygui/demo.py
@@ -875,7 +875,7 @@ def _selection(sender, app_data, user_data):
dpg.add_button(label="Button 3")
with dpg.tree_node(label="Groups"):
- dpg.add_text("Groups are used to control child items placement, width, and provide a hit box for things like is the set of items are hovered, ect...")
+ dpg.add_text("Groups are used to control child items placement, width, and provide a hit box for things like is the set of items are hovered, etc...")
with dpg.group(horizontal=True):
dpg.add_button(label="Button 1")
dpg.add_button(label="Button 2")
From 8e92bb194183a1054bfba92d9ac1d495ee91a7f1 Mon Sep 17 00:00:00 2001
From: Samuele Mazzi
Date: Tue, 4 Feb 2025 17:12:48 +0100
Subject: [PATCH 16/58] fix: fix `ToPyList` with `mvVec2/4` values
---
src/mvPyUtils.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/mvPyUtils.cpp b/src/mvPyUtils.cpp
index 24c5bf334..55edad283 100644
--- a/src/mvPyUtils.cpp
+++ b/src/mvPyUtils.cpp
@@ -624,7 +624,7 @@ ToPyList(const std::vector& value)
PyObject* item = PyList_New(2);
PyList_SetItem(item, 0, PyFloat_FromDouble (value[i].x));
PyList_SetItem(item, 1, PyFloat_FromDouble (value[i].y));
- PyList_SetItem(item, i, item);
+ PyList_SetItem(result, i, item);
}
return result;
@@ -644,7 +644,7 @@ ToPyList(const std::vector& value)
PyList_SetItem(item, 1, PyFloat_FromDouble(value[i].y));
PyList_SetItem(item, 2, PyFloat_FromDouble(value[i].z));
PyList_SetItem(item, 3, PyFloat_FromDouble(value[i].w));
- PyList_SetItem(item, i, item);
+ PyList_SetItem(result, i, item);
}
return result;
@@ -2974,4 +2974,4 @@ AddCommonArgs(std::vector& args, CommonParserArgs argsFlags
args.push_back({ mvPyDataType::Float, "track_offset", mvArgType::KEYWORD_ARG, "0.5", "0.0f:top, 0.5f:center, 1.0f:bottom" });
}
-}
\ No newline at end of file
+}
From 034d0cf47c96d5e4ac66364f0530a18c7aa5249e Mon Sep 17 00:00:00 2001
From: Vladimir Ein
Date: Fri, 20 Jun 2025 00:21:23 +0500
Subject: [PATCH 17/58] fix (win32): No more hanging up when the viewport it
resized. Removed WM_SIZING as it is not needed and was processed incorrectly
(#2401).
Reworked the message loop so that it processes all pending messages rather than one message per frame (#544, #1571, #2357).
Continue rendering when the viewport is being moved or resized by the user. Sending resize events, too (#1896, #2217).
Removed unused width/height fields from mvViewport.
---
src/dearpygui_commands.h | 2 +-
src/mvViewport.h | 4 +-
src/mvViewport_apple.mm | 7 +-
src/mvViewport_linux.cpp | 4 +-
src/mvViewport_win32.cpp | 265 +++++++++++++++++----------------------
5 files changed, 121 insertions(+), 161 deletions(-)
diff --git a/src/dearpygui_commands.h b/src/dearpygui_commands.h
index 457f2884b..74deb8704 100644
--- a/src/dearpygui_commands.h
+++ b/src/dearpygui_commands.h
@@ -2156,7 +2156,7 @@ create_viewport(PyObject* self, PyObject* args, PyObject* kwargs)
))
return GetPyNone();
- mvViewport* viewport = mvCreateViewport(width, height);
+ mvViewport* viewport = mvCreateViewport();
if (PyObject* item = PyDict_GetItemString(kwargs, "clear_color")) viewport->clearColor = ToColor(item);
if (PyObject* item = PyDict_GetItemString(kwargs, "small_icon")) viewport->small_icon = ToString(item);
if (PyObject* item = PyDict_GetItemString(kwargs, "large_icon")) viewport->large_icon = ToString(item);
diff --git a/src/mvViewport.h b/src/mvViewport.h
index c2b28c809..52e194b51 100644
--- a/src/mvViewport.h
+++ b/src/mvViewport.h
@@ -39,8 +39,6 @@ struct mvViewport
// position/size
b8 sizeDirty = false;
b8 posDirty = false;
- u32 width = 0;
- u32 height = 0;
u32 minwidth = 250;
u32 minheight = 250;
u32 maxwidth = 10000;
@@ -56,7 +54,7 @@ struct mvViewport
};
-mvViewport* mvCreateViewport (u32 width, u32 height);
+mvViewport* mvCreateViewport ();
void mvCleanupViewport (mvViewport& viewport);
void mvShowViewport (mvViewport& viewport, b8 minimized, b8 maximized);
void mvMaximizeViewport(mvViewport& viewport);
diff --git a/src/mvViewport_apple.mm b/src/mvViewport_apple.mm
index f468e6bfd..e6e41cf2d 100644
--- a/src/mvViewport_apple.mm
+++ b/src/mvViewport_apple.mm
@@ -11,11 +11,9 @@
#include
mvViewport*
-mvCreateViewport(unsigned width, unsigned height)
+mvCreateViewport()
{
auto viewport = new mvViewport();
- viewport->width = width;
- viewport->height = height;
viewport->platformSpecifics = new mvViewportData();
return viewport;
}
@@ -236,9 +234,6 @@
viewportData->layer.drawableSize = CGSizeMake(width, height);
id drawable = [viewportData->layer nextDrawable];
- viewport->width = (unsigned)width;
- viewport->height = (unsigned)height;
-
id commandBuffer = [graphicsData->commandQueue commandBuffer];
graphicsData->renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(viewport->clearColor.r,
viewport->clearColor.g,
diff --git a/src/mvViewport_linux.cpp b/src/mvViewport_linux.cpp
index eb5c2b642..b8b6496e3 100644
--- a/src/mvViewport_linux.cpp
+++ b/src/mvViewport_linux.cpp
@@ -107,11 +107,9 @@ mvPrerender()
}
mvViewport*
-mvCreateViewport(unsigned width, unsigned height)
+mvCreateViewport()
{
mvViewport* viewport = new mvViewport();
- viewport->width = width;
- viewport->height = height;
viewport->platformSpecifics = new mvViewportData();
return viewport;
}
diff --git a/src/mvViewport_win32.cpp b/src/mvViewport_win32.cpp
index 135823857..6e7757625 100644
--- a/src/mvViewport_win32.cpp
+++ b/src/mvViewport_win32.cpp
@@ -37,55 +37,86 @@ mvHandleModes(mvViewport& viewport)
}
+// Applies deferred changes to various viewport parameters set from Python via API
static void
-mvPrerender(mvViewport& viewport)
+ApplyViewportParms(mvViewport& viewport)
{
- MV_PROFILE_SCOPE("Viewport prerender")
+ mvViewportData* viewportData = (mvViewportData*)viewport.platformSpecifics;
- mvViewportData* viewportData = (mvViewportData*)viewport.platformSpecifics;
+ if (viewport.posDirty)
+ {
+ int horizontal_shift = get_horizontal_shift(viewportData->handle);
+ SetWindowPos(viewportData->handle, viewport.alwaysOnTop ? HWND_TOPMOST : HWND_TOP, viewport.xpos - horizontal_shift, viewport.ypos, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE);
+ viewport.posDirty = false;
+ }
- if (viewportData->msg.message == WM_QUIT)
- viewport.running = false;
+ if (viewport.sizeDirty)
+ {
+ SetWindowPos(viewportData->handle, viewport.alwaysOnTop ? HWND_TOPMOST : HWND_TOP, 0, 0, viewport.actualWidth, viewport.actualHeight, SWP_SHOWWINDOW | SWP_NOMOVE);
+ viewport.sizeDirty = false;
+ }
+ if (viewport.modesDirty)
{
- // TODO: we probably need a separate mutex for this
- std::lock_guard lk(GContext->mutex);
+ viewportData->modes = WS_OVERLAPPED;
- if (viewport.posDirty)
- {
- int horizontal_shift = get_horizontal_shift(viewportData->handle);
- SetWindowPos(viewportData->handle, viewport.alwaysOnTop ? HWND_TOPMOST : HWND_TOP, viewport.xpos - horizontal_shift, viewport.ypos, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE);
- viewport.posDirty = false;
+ if (viewport.resizable && viewport.decorated) viewportData->modes |= WS_THICKFRAME;
+ if (viewport.decorated) {
+ viewportData->modes |= WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
}
-
- if (viewport.sizeDirty)
- {
- SetWindowPos(viewportData->handle, viewport.alwaysOnTop ? HWND_TOPMOST : HWND_TOP, 0, 0, viewport.actualWidth, viewport.actualHeight, SWP_SHOWWINDOW | SWP_NOMOVE);
- viewport.sizeDirty = false;
+ else {
+ viewportData->modes |= WS_POPUP;
}
- if (viewport.modesDirty)
- {
- viewportData->modes = WS_OVERLAPPED;
+ SetWindowLongPtr(viewportData->handle, GWL_STYLE, viewportData->modes);
+ SetWindowPos(viewportData->handle, viewport.alwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
+ viewport.modesDirty = false;
+ }
- if (viewport.resizable && viewport.decorated) viewportData->modes |= WS_THICKFRAME;
- if (viewport.decorated) {
- viewportData->modes |= WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
- }
- else {
- viewportData->modes |= WS_POPUP;
- }
+ if (viewport.titleDirty)
+ {
+ SetWindowTextA(viewportData->handle, viewport.title.c_str());
+ viewport.titleDirty = false;
+ }
+}
- SetWindowLongPtr(viewportData->handle, GWL_STYLE, viewportData->modes);
- SetWindowPos(viewportData->handle, viewport.alwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
- viewport.modesDirty = false;
- }
+static void
+StartNewFrame()
+{
+ // Font manager is thread-unsafe, so we'd better sync it
+ std::lock_guard lk(GContext->mutex);
- if (viewport.titleDirty)
- {
- SetWindowTextA(viewportData->handle, viewport.title.c_str());
- viewport.titleDirty = false;
- }
+ if (mvToolManager::GetFontManager().isInvalid())
+ {
+ mvToolManager::GetFontManager().rebuildAtlas();
+ ImGui_ImplDX11_InvalidateDeviceObjects();
+ mvToolManager::GetFontManager().updateAtlas();
+ }
+
+ // Start the Dear ImGui frame
+ ImGui_ImplDX11_NewFrame();
+ ImGui_ImplWin32_NewFrame();
+
+ // Note: ImGui::NewFrame can conflict with get_text_size() on fonts:
+ // in particular, it can do SetCurrentFont() somewhere in the middle of
+ // get_text_size(), and thus ruin its measurements.
+ // That's why we cover NewFrame() with the mutex, too.
+ ImGui::NewFrame();
+}
+
+
+static bool
+mvPrerender(mvViewport& viewport)
+{
+ MV_PROFILE_SCOPE("Viewport prerender")
+
+ mvViewportData* viewportData = (mvViewportData*)viewport.platformSpecifics;
+
+ // An extra scope for mutex lock
+ {
+ // TODO: we probably need a separate mutex for this
+ std::lock_guard lk(GContext->mutex);
+ ApplyViewportParms(viewport);
}
// Poll and handle messages (inputs, window resize, etc.)
@@ -97,40 +128,32 @@ mvPrerender(mvViewport& viewport)
if (GContext->IO.waitForInput)
::WaitMessage();
- if (::PeekMessage(&viewportData->msg, nullptr, 0U, 0U, PM_REMOVE))
+ while (::PeekMessage(&viewportData->msg, nullptr, 0U, 0U, PM_REMOVE))
{
- ::TranslateMessage(&viewportData->msg);
- ::DispatchMessage(&viewportData->msg);
- //continue;
- }
-
- {
- // Font manager is thread-unsafe, so we'd better sync it
- std::lock_guard lk(GContext->mutex);
-
- if (mvToolManager::GetFontManager().isInvalid())
+ if (viewportData->msg.message == WM_QUIT)
+ {
+ viewport.running = false;
+ return false;
+ }
+ else
{
- mvToolManager::GetFontManager().rebuildAtlas();
- ImGui_ImplDX11_InvalidateDeviceObjects();
- mvToolManager::GetFontManager().updateAtlas();
+ ::TranslateMessage(&viewportData->msg);
+ ::DispatchMessage(&viewportData->msg);
}
}
- // Start the Dear ImGui frame
- ImGui_ImplDX11_NewFrame();
- ImGui_ImplWin32_NewFrame();
- ImGui::NewFrame();
-
+ StartNewFrame();
+ return true;
}
+const UINT_PTR resizeTimerID = 1;
+
static LRESULT
mvHandleMsg(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept
{
if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
return true;
- static UINT_PTR puIDEvent = 0;
-
mvViewport* viewport = GContext->viewport;
mvGraphics& graphics = GContext->graphics;
mvViewportData* viewportData = (mvViewportData*)viewport->platformSpecifics;
@@ -139,64 +162,6 @@ mvHandleMsg(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept
switch (msg)
{
- case WM_PAINT:
- {
- if (GContext->frame > 0)
- {
-
- RECT rect;
- RECT crect;
- int awidth = 0;
- int aheight = 0;
- int cwidth = 0;
- int cheight = 0;
- if (GetWindowRect(hWnd, &rect))
- {
- awidth = rect.right - rect.left;
- aheight = rect.bottom - rect.top;
- }
-
- if (GetClientRect(hWnd, &crect))
- {
- cwidth = crect.right - crect.left;
- cheight = crect.bottom - crect.top;
- }
-
- {
- std::lock_guard lk(GContext->mutex);
-
- viewport->actualWidth = awidth;
- viewport->actualHeight = aheight;
-
-
- GContext->viewport->clientHeight = cheight;
- GContext->viewport->clientWidth = cwidth;
-
- //GContext->viewport->resized = true;
- mvOnResize();
- GContext->viewport->resized = false;
-
- if (mvToolManager::GetFontManager().isInvalid())
- {
- mvToolManager::GetFontManager().rebuildAtlas();
- ImGui_ImplDX11_InvalidateDeviceObjects();
- mvToolManager::GetFontManager().updateAtlas();
- }
- }
- // Start the Dear ImGui frame
- ImGui_ImplDX11_NewFrame();
- ImGui_ImplWin32_NewFrame();
- ImGui::NewFrame();
- Render();
- present(graphics, GContext->viewport->clearColor, GContext->viewport->vsync);
- }
- // must be called for the OS to do its thing
- PAINTSTRUCT tPaint;
- HDC tDeviceContext = BeginPaint(hWnd, &tPaint);
- EndPaint(hWnd, &tPaint);
- break;
- }
-
case WM_GETMINMAXINFO:
{
// TODO: lock the mutex?
@@ -209,19 +174,28 @@ mvHandleMsg(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept
break;
}
- case WM_MOVING:
+ case WM_MOVE:
{
std::lock_guard lk(GContext->mutex);
- int horizontal_shift = get_horizontal_shift(viewportData->handle);
- RECT rect = *(RECT*)(lParam);
- viewport->xpos = rect.left + horizontal_shift;
- viewport->ypos = rect.top;
+ // We explicitly ignore all WM_MOVE messages until the rendering loop
+ // starts. This is because on Windows 10 and later, the coordinates passed
+ // to CreateWindow don't point to the *visible* top left corner of the window
+ // (see DWMWA_EXTENDED_FRAME_BOUNDS - there's transparent padding around the window).
+ // ApplyViewportParms, called from mvPrerender, moves the viewport to correct
+ // coordinates, but this will only happen if we don't spoil xpos/ypos in a WM_MOVE
+ // sent before the first call to mvPrerender (e.g. in response to ShowWindow).
+ RECT rect;
+ if (GContext->frame > 0 && GetWindowRect(hWnd, &rect))
+ {
+ int horizontal_shift = get_horizontal_shift(viewportData->handle);
+ viewport->xpos = rect.left + horizontal_shift;
+ viewport->ypos = rect.top;
+ }
break;
}
case WM_SIZE:
- case WM_SIZING:
if (graphicsData != nullptr && wParam != SIZE_MINIMIZED)
{
@@ -247,25 +221,10 @@ mvHandleMsg(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept
viewport->actualWidth = awidth;
viewport->actualHeight = aheight;
+ viewport->clientWidth = cwidth;
+ viewport->clientHeight = cheight;
-
- if (viewport->decorated)
- {
- GContext->viewport->clientHeight = cheight;
- GContext->viewport->clientWidth = cwidth;
- }
- else
- {
- GContext->viewport->clientHeight = cheight;
- GContext->viewport->clientWidth = cwidth;
- }
-
- GContext->viewport->resized = true;
- //mvOnResize();
-
- // I believe this are only used for the error logger
- viewport->width = (UINT)LOWORD(lParam);
- viewport->height = (UINT)HIWORD(lParam);
+ viewport->resized = true;
if (viewport->decorated)
resize_swapchain(graphics, (int)(UINT)LOWORD(lParam), (int)(UINT)HIWORD(lParam));
@@ -280,21 +239,32 @@ mvHandleMsg(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept
// Timer events can still be caught so here we add a timer so we
// can continue rendering when catching the WM_TIMER event.
// Timer is killed in the WM_EXITSIZEMOVE case below.
- puIDEvent = SetTimer(NULL, puIDEvent, USER_TIMER_MINIMUM, NULL);
- SetTimer(hWnd, puIDEvent, USER_TIMER_MINIMUM, NULL);
+ SetTimer(hWnd, resizeTimerID, USER_TIMER_MINIMUM, NULL);
break;
}
case WM_EXITSIZEMOVE:
{
- KillTimer(hWnd, puIDEvent);
+ KillTimer(hWnd, resizeTimerID);
break;
}
case WM_TIMER:
{
- if (wParam == puIDEvent)
- mvOnResize();
+ if (wParam == resizeTimerID)
+ {
+ // TODO: we probably need a separate mutex for ApplyViewportParms
+ std::lock_guard lk(GContext->mutex);
+ ApplyViewportParms(*viewport);
+ StartNewFrame();
+ Render();
+ present(graphics, viewport->clearColor, viewport->vsync);
+ if (viewport->resized)
+ {
+ mvOnResize();
+ viewport->resized = false;
+ }
+ }
break;
}
case WM_SYSCOMMAND:
@@ -361,11 +331,9 @@ mvHandleMsg(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept
}
mvViewport*
-mvCreateViewport(unsigned width, unsigned height)
+mvCreateViewport()
{
mvViewport* viewport = new mvViewport();
- viewport->width = width;
- viewport->height = height;
viewport->platformSpecifics = new mvViewportData();
return viewport;
}
@@ -496,7 +464,8 @@ mvCleanupViewport(mvViewport& viewport)
void
mvRenderFrame()
{
- mvPrerender(*GContext->viewport);
+ if (!mvPrerender(*GContext->viewport))
+ return;
Render();
present(GContext->graphics, GContext->viewport->clearColor, GContext->viewport->vsync);
}
From e58ac3747501330b36961bac4394aec983c91b5b Mon Sep 17 00:00:00 2001
From: Vladimir Ein
Date: Mon, 23 Jun 2025 20:28:32 +0500
Subject: [PATCH 18/58] fix (win32): Invoking viewport_resize_callback at the
very start (fixes a regression in #2521).
---
src/mvViewport_win32.cpp | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/src/mvViewport_win32.cpp b/src/mvViewport_win32.cpp
index 6e7757625..0e5a29af8 100644
--- a/src/mvViewport_win32.cpp
+++ b/src/mvViewport_win32.cpp
@@ -197,7 +197,7 @@ mvHandleMsg(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept
case WM_SIZE:
- if (graphicsData != nullptr && wParam != SIZE_MINIMIZED)
+ if (wParam != SIZE_MINIMIZED)
{
RECT rect;
RECT crect;
@@ -226,10 +226,13 @@ mvHandleMsg(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept
viewport->resized = true;
- if (viewport->decorated)
- resize_swapchain(graphics, (int)(UINT)LOWORD(lParam), (int)(UINT)HIWORD(lParam));
- else
- resize_swapchain(graphics, awidth, aheight);
+ if (graphicsData != nullptr)
+ {
+ if (viewport->decorated)
+ resize_swapchain(graphics, (int)(UINT)LOWORD(lParam), (int)(UINT)HIWORD(lParam));
+ else
+ resize_swapchain(graphics, awidth, aheight);
+ }
}
return 0;
From 80360a8aebf3763df42d88e7f1e306673f301b98 Mon Sep 17 00:00:00 2001
From: Jonathan Hoffstadt
Date: Mon, 23 Jun 2025 21:12:27 -0500
Subject: [PATCH 19/58] chore: bump version to v2.1.0
---
.github/workflows/Deployment.yml | 2 +-
setup.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/Deployment.yml b/.github/workflows/Deployment.yml
index 8b6b695fc..cbb094acd 100644
--- a/.github/workflows/Deployment.yml
+++ b/.github/workflows/Deployment.yml
@@ -14,7 +14,7 @@ on:
version:
description: 'Dear PyGui Version'
required: false
- default: '2.0.0b1'
+ default: '2.1.0'
deploy:
description: 'Deploy (true will deploy to pypi)'
diff --git a/setup.py b/setup.py
index 8198c8a48..c35ef0dc3 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@
import shutil
import subprocess
-wip_version = "2.0.0"
+wip_version = "2.1.0"
def version_number():
"""This function reads the version number which is populated by github actions"""
From 57d10ff98b7d60a4725df17f5c39b2205bf80ebd Mon Sep 17 00:00:00 2001
From: nvglucifer <77535212+nvglucifer@users.noreply.github.com>
Date: Wed, 30 Jul 2025 22:40:36 +0700
Subject: [PATCH 20/58] docs (plot.rst): update plot querying documentation
---
docs/source/documentation/plots.rst | 31 +++++++++++++++++++++--------
1 file changed, 23 insertions(+), 8 deletions(-)
diff --git a/docs/source/documentation/plots.rst b/docs/source/documentation/plots.rst
index fa6a43ee2..07489fb2a 100644
--- a/docs/source/documentation/plots.rst
+++ b/docs/source/documentation/plots.rst
@@ -342,14 +342,23 @@ Querying
--------
Querying allows the user to select a region of the plot by
-holding with the right mouse button and clicking with the left one.
+**Ctrl + dragging the right mouse button** and dragging five
+circles of the query rectangle with left mouse button.
+
+Double left click inside a drag rect will remove it (if *min_query_rects* allows).
+If number of rects exceed *max_query_rects* when create new drag rect, it will replace the last one.
+
+Since DearPyGui 2.0, *query_mod* changes to *query_toggle_mod* for swapping the Ctrl key above.
+*query_button* is removed, so **dragging the right mouse button** is hardcoded.
+*min_query_rects, max_query_rects* limit the number of drag rects.
Querying requires setting *query* to **True** when creating the plot.
-The callback of the plot will run when the plot is being queried.
+The callback of the plot will run when the plot is being queried (dragging five circles).
+Or not using plot's callback but drag rect's callback *dpg.add_drag_rect(callback=...)*.
All the query areas are sent through the *app_data* argument as
-*[(x_min, x_max, y_min, y_max), (x_min, x_max, y_min, y_max), ...]*.
+*((x_min, y_min, x_max, y_max), (x_min, y_min, x_max, y_max), ...)*.
It is also possible to poll the plot for the query areas by calling:
:py:func:`get_plot_query_rects ` and
@@ -370,16 +379,22 @@ Below is an example using the callback
sindatay.append(0.5 + 0.5 * sin(50 * i / 100))
with dpg.window(label="Tutorial", width=400, height=600):
- dpg.add_text("Click and drag the middle mouse button over the top plot!")
+ dpg.add_text("Ctrl and drag the right mouse button over the top plot!")
def query(sender, app_data, user_data):
- dpg.set_axis_limits("xaxis_tag2", app_data[0], app_data[1])
- dpg.set_axis_limits("yaxis_tag2", app_data[2], app_data[3])
+ # TODO: handle for when app_data is empty - IndexError: tuple index out of range.
+ rect_0 = app_data[0]
+ # other_rects = app_data[1:]
+ dpg.set_axis_limits("xaxis_tag2", rect_0[0], rect_0[2])
+ dpg.set_axis_limits("yaxis_tag2", rect_0[1], rect_0[3])
# plot 1
- with dpg.plot(no_title=True, height=200, callback=query, query=True, no_menus=True, width=-1):
+ with dpg.plot(
+ no_title=True, height=200, callback=query, query=True, no_menus=True, width=-1,
+ min_query_rects=0, max_query_rects=3,
+ ):
dpg.add_plot_axis(dpg.mvXAxis, label="x")
dpg.add_plot_axis(dpg.mvYAxis, label="y")
dpg.add_line_series(sindatax, sindatay, parent=dpg.last_item())
@@ -593,4 +608,4 @@ Gallery
.. image:: https://raw.githubusercontent.com/wiki/epezent/implot/screenshots3/heat.gif
-.. image:: https://raw.githubusercontent.com/wiki/epezent/implot/screenshots3/tables.gif
\ No newline at end of file
+.. image:: https://raw.githubusercontent.com/wiki/epezent/implot/screenshots3/tables.gif
From 40355739b7b1be4b063b2cfc919efbdcc124fb64 Mon Sep 17 00:00:00 2001
From: yasserhcn
Date: Sat, 6 Sep 2025 22:02:13 +0100
Subject: [PATCH 21/58] doc: Updated the docs regarding experimental window
docking
---
docs/source/extra/video-tutorials.rst | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/source/extra/video-tutorials.rst b/docs/source/extra/video-tutorials.rst
index a10f48482..9ba6582ba 100644
--- a/docs/source/extra/video-tutorials.rst
+++ b/docs/source/extra/video-tutorials.rst
@@ -221,15 +221,15 @@ with a callback close_popup("popup1").
The docking feature enables the user to dock windows to each other and the viewport.
-The docking feature is not documented yet (as of January 2021).
+The docking feature is not documented yet (as of September 2025).
-enable_docking() will enable experimental docking.
+Setting the `docking` argument to `True` in `configure_app()` will enable experimental docking.
By default, the user needs to hold the shift key to enable docking.
-The keyword shift_only = False enables docking without holding the shift key.
+Setting `docking_shift_only = False` enables docking without holding the shift key.
-The keyword dock_space = True enables docking windows to the viewport.
+Setting `docking_space = True` enables docking windows to the viewport.
The docking feature is experimental because you cannot programmatically
set up the docking positions.
From 6a299f312203e084cb3fd9b6b9101f1bad772716 Mon Sep 17 00:00:00 2001
From: nvglucifer <77535212+nvglucifer@users.noreply.github.com>
Date: Tue, 7 Oct 2025 15:01:52 +0700
Subject: [PATCH 22/58] Fix (mvThemes.cpp): Add missing
ImGuiStyleVar_TabBarOverlineSize (only for
ImGuiStyleVar_TableAngledHeadersAngle to work)
Add missing ImGuiStyleVar_TabBarOverlineSize (only for ImGuiStyleVar_TableAngledHeadersAngle to work).
fixing an issue about: TableAngledHeaderAngle does not work outside of Style Editor (https://discord.com/channels/736279277242417272/1424799834006884483)
---
src/mvThemes.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/mvThemes.cpp b/src/mvThemes.cpp
index 749e40671..b01cd1247 100644
--- a/src/mvThemes.cpp
+++ b/src/mvThemes.cpp
@@ -388,6 +388,7 @@ static const mvGuiStyleVarInfo GStyleVarInfo[] =
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBorderSize) }, // ImGuiStyleVar_TabBorderSize
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBarBorderSize) }, // ImGuiStyleVar_TabBarBorderSize
+ { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBarOverlineSize) }, // ImGuiStyleVar_TabBarOverlineSize
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersAngle)}, // ImGuiStyleVar_TableAngledHeadersAngle
{ ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersTextAlign)},// ImGuiStyleVar_TableAngledHeadersTextAlign
{ ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign
From 478b9dfc2495c7bbecd72ec23a4976ea08cced87 Mon Sep 17 00:00:00 2001
From: Jonathan Hoffstadt
Date: Tue, 28 Oct 2025 15:17:46 -0500
Subject: [PATCH 23/58] feat: add python 3.14 support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: BrayanMoises RodrĂguezVeizaga
---
.github/workflows/Deployment.yml | 8 ++++----
setup.py | 1 +
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/Deployment.yml b/.github/workflows/Deployment.yml
index cbb094acd..e98ed6f02 100644
--- a/.github/workflows/Deployment.yml
+++ b/.github/workflows/Deployment.yml
@@ -33,7 +33,7 @@ jobs:
runs-on: windows-2022
strategy:
matrix:
- python-version: [ 3.8, 3.9, "3.10", "3.11", "3.12", "3.13" ]
+ python-version: [ 3.8, 3.9, "3.10", "3.11", "3.12", "3.13", "3.14" ]
steps:
@@ -77,7 +77,7 @@ jobs:
runs-on: macos-13
strategy:
matrix:
- python-version: [ 3.8, 3.9, "3.10", "3.11", "3.12", "3.13" ]
+ python-version: [ 3.8, 3.9, "3.10", "3.11", "3.12", "3.13", "3.14" ]
steps:
@@ -118,7 +118,7 @@ jobs:
runs-on: macos-latest-xlarge
strategy:
matrix:
- python-version: [ "3.10", "3.11", "3.12", "3.13" ]
+ python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14" ]
steps:
@@ -161,7 +161,7 @@ jobs:
CXX: g++-9
strategy:
matrix:
- python-version: [ 3.8, 3.9, "3.10", "3.11", "3.12", "3.13" ]
+ python-version: [ 3.8, 3.9, "3.10", "3.11", "3.12", "3.13", "3.14" ]
steps:
diff --git a/setup.py b/setup.py
index c35ef0dc3..bb7a1e011 100644
--- a/setup.py
+++ b/setup.py
@@ -186,6 +186,7 @@ def setup_package():
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
+ 'Programming Language :: Python :: 3.14',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: 3 :: Only',
'Topic :: Software Development :: User Interfaces',
From 350745b2bf851d335ecfc83cf07028d877c41858 Mon Sep 17 00:00:00 2001
From: Jonathan Hoffstadt
Date: Tue, 28 Oct 2025 15:45:32 -0500
Subject: [PATCH 24/58] chore: bump version to 2.1.1
---
.github/workflows/Deployment.yml | 2 +-
setup.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/Deployment.yml b/.github/workflows/Deployment.yml
index e98ed6f02..8e81623d3 100644
--- a/.github/workflows/Deployment.yml
+++ b/.github/workflows/Deployment.yml
@@ -14,7 +14,7 @@ on:
version:
description: 'Dear PyGui Version'
required: false
- default: '2.1.0'
+ default: '2.1.2'
deploy:
description: 'Deploy (true will deploy to pypi)'
diff --git a/setup.py b/setup.py
index bb7a1e011..c8d3c0523 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@
import shutil
import subprocess
-wip_version = "2.1.0"
+wip_version = "2.1.1"
def version_number():
"""This function reads the version number which is populated by github actions"""
From 8369d7c37b470e816405bd411e54992eeece4e60 Mon Sep 17 00:00:00 2001
From: Jonathan Hoffstadt
Date: Fri, 14 Nov 2025 08:12:28 -0600
Subject: [PATCH 25/58] ci: increase linux runners g++ versions
---
.github/workflows/Deployment.yml | 2 +-
.github/workflows/EmbeddedBuild.yml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/Deployment.yml b/.github/workflows/Deployment.yml
index 8e81623d3..1c78a8ab8 100644
--- a/.github/workflows/Deployment.yml
+++ b/.github/workflows/Deployment.yml
@@ -158,7 +158,7 @@ jobs:
runs-on: ubuntu-22.04
env:
- CXX: g++-9
+ CXX: g++-10
strategy:
matrix:
python-version: [ 3.8, 3.9, "3.10", "3.11", "3.12", "3.13", "3.14" ]
diff --git a/.github/workflows/EmbeddedBuild.yml b/.github/workflows/EmbeddedBuild.yml
index 08ca9bc55..03f23e342 100644
--- a/.github/workflows/EmbeddedBuild.yml
+++ b/.github/workflows/EmbeddedBuild.yml
@@ -99,7 +99,7 @@ jobs:
runs-on: ubuntu-22.04
env:
- CXX: g++-9
+ CXX: g++-10
if: ${{! contains(github.event.head_commit.message, '[individual]') || contains(github.event.head_commit.message, '[linux]')}}
steps:
From 736a0b7b31c4501d03685455ff8c306615570a52 Mon Sep 17 00:00:00 2001
From: Vladimir Ein
Date: Sun, 14 Dec 2025 12:47:31 +0500
Subject: [PATCH 26/58] fix (callbacks): A large rework of the callback
mechanism that fixes unbalanced INCREF/DECREF calls and prevents leaks and
some other issues #2036
---
src/dearpygui_commands.h | 243 ++++++++++++--------------
src/mvAppItem.cpp | 128 +++++++++-----
src/mvAppItem.h | 43 ++++-
src/mvBasicWidgets.cpp | 259 ++++------------------------
src/mvCallbackRegistry.cpp | 342 +++++++------------------------------
src/mvCallbackRegistry.h | 121 ++++++++++---
src/mvColors.cpp | 29 +---
src/mvContainers.cpp | 164 +++++++++---------
src/mvContainers.h | 16 +-
src/mvContext.cpp | 5 +
src/mvContext.h | 6 +
src/mvDatePicker.cpp | 8 +-
src/mvDrawings.cpp | 5 +-
src/mvFileDialog.cpp | 38 ++---
src/mvFileDialog.h | 2 +-
src/mvGlobalHandlers.cpp | 151 +++-------------
src/mvItemHandlers.cpp | 168 +++++-------------
src/mvItemHandlers.h | 59 ++++---
src/mvItemRegistry.cpp | 5 +-
src/mvItemRegistry.h | 4 +-
src/mvLayoutWindow.cpp | 2 +-
src/mvNodes.cpp | 41 +----
src/mvNodes.h | 2 +-
src/mvPlotting.cpp | 78 +++------
src/mvPyUtils.cpp | 48 ++----
src/mvPyUtils.h | 20 ++-
src/mvSlider3D.cpp | 9 +-
src/mvTables.cpp | 15 +-
src/mvTextureItems.cpp | 8 +-
src/mvTextureItems.h | 2 +-
src/mvTimePicker.cpp | 8 +-
src/mvViewport.h | 29 +++-
src/mvViewport_apple.mm | 6 +-
src/mvViewport_linux.cpp | 8 +-
src/mvViewport_win32.cpp | 6 +-
35 files changed, 753 insertions(+), 1325 deletions(-)
diff --git a/src/dearpygui_commands.h b/src/dearpygui_commands.h
index 74deb8704..4b68a149e 100644
--- a/src/dearpygui_commands.h
+++ b/src/dearpygui_commands.h
@@ -2006,19 +2006,13 @@ set_frame_callback(PyObject* self, PyObject* args, PyObject* kwargs)
&frame, &callback, &user_data))
return GetPyNone();
+ std::lock_guard lk(GContext->mutex);
+
if (frame > GContext->callbackRegistry->highestFrame)
GContext->callbackRegistry->highestFrame = frame;
- // TODO: check previous entry and deprecate if existing
- Py_XINCREF(callback);
-
- if(user_data)
- Py_XINCREF(user_data);
- mvSubmitCallback([=]()
- {
- GContext->callbackRegistry->frameCallbacks[frame] = callback;
- GContext->callbackRegistry->frameCallbacksUserData[frame] = user_data;
- });
+ GContext->callbackRegistry->frameCallbacks.insert_or_assign(frame, mvPyObject(callback, true));
+ GContext->callbackRegistry->frameCallbacksUserData.insert_or_assign(frame, mvPyObject(user_data, true));
return GetPyNone();
}
@@ -2033,14 +2027,9 @@ set_exit_callback(PyObject* self, PyObject* args, PyObject* kwargs)
&user_data))
return GetPyNone();
- Py_XINCREF(callback);
- if(user_data)
- Py_XINCREF(user_data);
- mvSubmitCallback([=]()
- {
- GContext->callbackRegistry->onCloseCallback = SanitizeCallback(callback);
- GContext->callbackRegistry->onCloseCallbackUserData = user_data;
- });
+ *GContext->callbackRegistry->onCloseCallback = mvPyObject(callback == Py_None? nullptr : callback, true);
+ *GContext->callbackRegistry->onCloseCallbackUserData = mvPyObject(user_data, true);
+
return GetPyNone();
}
@@ -2054,17 +2043,8 @@ set_viewport_resize_callback(PyObject* self, PyObject* args, PyObject* kwargs)
&callback, &user_data))
return GetPyNone();
- if (callback)
- Py_XINCREF(callback);
-
- if (user_data)
- Py_XINCREF(user_data);
-
- mvSubmitCallback([=]()
- {
- GContext->callbackRegistry->resizeCallback = SanitizeCallback(callback);
- GContext->callbackRegistry->resizeCallbackUserData = user_data;
- });
+ *GContext->callbackRegistry->resizeCallback = mvPyObject(callback == Py_None? nullptr : callback, true);
+ *GContext->callbackRegistry->resizeCallbackUserData = mvPyObject(user_data, true);
return GetPyNone();
}
@@ -2527,14 +2507,26 @@ output_frame_buffer(PyObject* self, PyObject* args, PyObject* kwargs)
if (filepathLength == 0 && callback) // not specified, return array instead
{
- //Py_XINCREF(callback);
PyObject* newbuffer = nullptr;
PymvBuffer* newbufferview = PyObject_New(PymvBuffer, &PymvBufferType);
newbuffer = PyObject_Init((PyObject*)newbufferview, &PymvBufferType);
- mvSubmitTask([newbuffer, callback, newbufferview]() {
+
+ // Making an owned ref while we're still holding GIL (can't do this within mvSubmitTask).
+ auto stored_callback = std::make_shared(callback, true);
+ // We need to schedule this into the rendering thread because OutputFrameBufferArray
+ // accesses the rendering API, which might well have thread-local things in the context.
+ mvSubmitTask([stored_callback, newbuffer, newbufferview]() {
OutputFrameBufferArray(newbufferview);
- mvAddCallback(callback, 0, newbuffer, nullptr, false);
- });
+ mvAddOwnerlessCallback(
+ stored_callback, std::make_shared(nullptr),
+ 0, "",
+ // Note: the callback queue will DECREF the value returned by this
+ // lambda, effectively deleting `newbuffer` so that we don't need
+ // to perform any special cleanup. We just pass the value as it is,
+ // keeping a refcount of 1 all the time until the callback is done.
+ [=]() { return newbuffer; }
+ );
+ });
return GetPyNone();
}
@@ -2571,7 +2563,7 @@ output_frame_buffer(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
is_dearpygui_running(PyObject* self, PyObject* args, PyObject* kwargs)
{
- return ToPyBool(GContext->started);
+ return ToPyBool(GContext->running);
}
static PyObject*
@@ -2591,6 +2583,7 @@ setup_dearpygui(PyObject* self, PyObject* args, PyObject* kwargs)
while (!GContext->itemRegistry->containers.empty())
GContext->itemRegistry->containers.pop();
GContext->started = true;
+ GContext->running = true;
GContext->future = std::async(std::launch::async, []() {return mvRunCallbacks(); });
Py_END_ALLOW_THREADS;
return GetPyNone();
@@ -2646,10 +2639,6 @@ create_context(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
destroy_context(PyObject* self, PyObject* args, PyObject* kwargs)
{
- // std::lock_guard lk(GContext->mutex);
-
- Py_BEGIN_ALLOW_THREADS;
-
if (GContext == nullptr)
{
assert(false);
@@ -2657,47 +2646,57 @@ destroy_context(PyObject* self, PyObject* args, PyObject* kwargs)
else
{
- // hacky fix, started was set to false
- // to exit the event loop, but needs to be
- // true in order to run DPG commands for the
- // exit callback.
- GContext->started = true;
- mvSubmitCallback([=]() {
- mvRunCallback(GContext->callbackRegistry->onCloseCallback, 0, nullptr, GContext->callbackRegistry->onCloseCallbackUserData);
- GContext->started = false; // return to false after
- });
-
- if (GContext->viewport != nullptr)
- mvCleanupViewport(*GContext->viewport);
-
- ImNodes::DestroyContext();
- ImPlot::DestroyContext();
- ImGui::DestroyContext();
+ // Make sure everyone knows we're shutting down, even if stop_dearpygui
+ // was not called.
+ StopRendering();
- mvToolManager::Reset();
- ClearItemRegistry(*GContext->itemRegistry);
+ Py_BEGIN_ALLOW_THREADS;
+ // Queue the close callback, if any. The environment is still healthy enough
+ // for it to run, except that no more frames will be rendered with the current GContext.
+ mvAddOwnerlessCallback(GContext->callbackRegistry->onCloseCallback, GContext->callbackRegistry->onCloseCallbackUserData);
-
- //#define X(el) el::s_class_theme_component = nullptr; el::s_class_theme_disabled_component = nullptr;
- #define X(el) DearPyGui::GetClassThemeComponent(mvAppItemType::el) = nullptr; DearPyGui::GetDisabledClassThemeComponent(mvAppItemType::el) = nullptr;
- MV_ITEM_TYPES
- #undef X
-
- mvSubmitCallback([=]() {
+ // Shutting down the callback loop - this will run right after the close callback
+ mvSubmitCallback([]() {
GContext->callbackRegistry->running = false;
- });
+ }, true);
+ // Waiting for it to complete all tasks and shut down
if (GContext->future.valid())
GContext->future.get();
- if (GContext->viewport)
- delete GContext->viewport;
- delete GContext->itemRegistry;
- delete GContext->callbackRegistry;
- delete GContext;
- GContext = nullptr;
+ // The rest of cleanup must be done with GIL locked because it might lead
+ // to occasional DECREFs on Python objects (e.g. a callback or user_data).
+ Py_END_ALLOW_THREADS;
+
+ mvContext* context_to_delete = nullptr;
+ {
+ // Even though the handlers thread is down, there's still a chance that
+ // the user calls DPG from another Python thread. We'd better lock the
+ // mutex while we're tinkering with all the global structures.
+ std::lock_guard lk(GContext->mutex);
+
+ mvToolManager::Reset();
+ ClearItemRegistry(*GContext->itemRegistry);
+
+ ImNodes::DestroyContext();
+ ImPlot::DestroyContext();
+ ImGui::DestroyContext();
+
+ #define X(el) DearPyGui::GetClassThemeComponent(mvAppItemType::el) = nullptr; DearPyGui::GetDisabledClassThemeComponent(mvAppItemType::el) = nullptr;
+ MV_ITEM_TYPES
+ X(All)
+ #undef X
+
+ if (GContext->viewport)
+ delete GContext->viewport;
+
+ delete GContext->itemRegistry;
+ delete GContext->callbackRegistry;
+ context_to_delete = GContext;
+ GContext = nullptr;
+ }
+ delete context_to_delete;
}
- Py_END_ALLOW_THREADS;
return GetPyNone();
}
@@ -2705,8 +2704,8 @@ destroy_context(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
stop_dearpygui(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
- GContext->started = false;
+ StopRendering();
+ std::lock_guard lk(GContext->mutex);
auto viewport = GContext->viewport;
if (viewport)
viewport->running = false;
@@ -3771,37 +3770,17 @@ get_item_configuration(PyObject* self, PyObject* args, PyObject* kwargs)
PyDict_SetItemString(pdict, "height", py_height);
PyDict_SetItemString(pdict, "indent", py_indent);
- if (appitem->config.callback)
- {
- Py_XINCREF(appitem->config.callback);
- PyDict_SetItemString(pdict, "callback", appitem->config.callback);
- }
- else
- PyDict_SetItemString(pdict, "callback", GetPyNone());
+ PyObject* callback = appitem->config.callback;
+ PyDict_SetItemString(pdict, "callback", callback? callback : Py_None);
- if (appitem->config.dropCallback)
- {
- Py_XINCREF(appitem->config.dropCallback);
- PyDict_SetItemString(pdict, "drop_callback", appitem->config.dropCallback);
- }
- else
- PyDict_SetItemString(pdict, "drop_callback", GetPyNone());
+ PyObject* dropCallback = appitem->config.dropCallback;
+ PyDict_SetItemString(pdict, "drop_callback", dropCallback? dropCallback : Py_None);
- if (appitem->config.dragCallback)
- {
- Py_XINCREF(appitem->config.dragCallback);
- PyDict_SetItemString(pdict, "drag_callback", appitem->config.dragCallback);
- }
- else
- PyDict_SetItemString(pdict, "drag_callback", GetPyNone());
+ PyObject* dragCallback = appitem->config.dragCallback;
+ PyDict_SetItemString(pdict, "drag_callback", dragCallback? dragCallback : Py_None);
- if (appitem->config.user_data)
- {
- Py_XINCREF(appitem->config.user_data);
- PyDict_SetItemString(pdict, "user_data", appitem->config.user_data);
- }
- else
- PyDict_SetItemString(pdict, "user_data", GetPyNone());
+ PyObject* user_data = *(appitem->config.user_data);
+ PyDict_SetItemString(pdict, "user_data", user_data? user_data : Py_None);
appitem->getSpecificConfiguration(pdict);
}
@@ -4229,21 +4208,8 @@ capture_next_item(PyObject* self, PyObject* args, PyObject* kwargs)
std::lock_guard lk(GContext->mutex);
- if (GContext->itemRegistry->captureCallback)
- Py_XDECREF(GContext->itemRegistry->captureCallback);
-
- if (GContext->itemRegistry->captureCallbackUserData)
- Py_XDECREF(GContext->itemRegistry->captureCallbackUserData);
-
- Py_XINCREF(callable);
- if(user_data)
- Py_XINCREF(user_data);
- if (callable == Py_None)
- GContext->itemRegistry->captureCallback = nullptr;
- else
- GContext->itemRegistry->captureCallback = callable;
-
- GContext->itemRegistry->captureCallbackUserData = user_data;
+ GContext->itemRegistry->captureCallback = mvPyObject(callable == Py_None? nullptr : callable, true);
+ GContext->itemRegistry->captureCallbackUserData = mvPyObject(user_data, true);
return GetPyNone();
}
@@ -4254,29 +4220,44 @@ get_callback_queue(PyObject* self, PyObject* args, PyObject* kwargs)
if (GContext->callbackRegistry->jobs.empty())
return GetPyNone();
+ std::lock_guard lk(GContext->mutex);
+
PyObject* pArgs = PyTuple_New(GContext->callbackRegistry->jobs.size());
for (int i = 0; i < GContext->callbackRegistry->jobs.size(); i++)
{
PyObject* job = PyTuple_New(4);
- if (GContext->callbackRegistry->jobs[i].callback)
- PyTuple_SetItem(job, 0, GContext->callbackRegistry->jobs[i].callback);
- else
- PyTuple_SetItem(job, 0, GetPyNone());
+ const mvCallbackJob& cur_entry = GContext->callbackRegistry->jobs[i];
- if(GContext->callbackRegistry->jobs[i].sender == 0)
- PyTuple_SetItem(job, 1, ToPyString(GContext->callbackRegistry->jobs[i].sender_str));
+ PyObject* callback;
+ if (cur_entry.ownerless_callback)
+ {
+ callback = *cur_entry.ownerless_callback;
+ Py_XINCREF(callback);
+ }
else
- PyTuple_SetItem(job, 1, ToPyUUID(GContext->callbackRegistry->jobs[i].sender));
+ {
+ auto liveOwner = cur_entry.owner.lock();
+ // If the owner of this entry is gone, we'll just set the callback to None.
+ // This lets us create the output list right away, without the need to collect
+ // valid callbacks first. Also, this mimicks the behavior of widgets without
+ // a `callback` set on them, which in the "manual" mode put null callbacks into
+ // the queue. It's mostly a debug/diagnostic mode anyway - captures everything.
+ callback = liveOwner? cur_entry.callback : nullptr;
+ // Must only be done while we own liveOwner.
+ Py_XINCREF(callback);
+ }
+ PyTuple_SetItem(job, 0, callback? callback : GetPyNone());
- if (GContext->callbackRegistry->jobs[i].app_data)
- PyTuple_SetItem(job, 2, GContext->callbackRegistry->jobs[i].app_data); // steals data, so don't deref
- else
- PyTuple_SetItem(job, 2, GetPyNone());
+ PyTuple_SetItem(job, 1, ToPyUUID(cur_entry.sender, cur_entry.alias));
- if (GContext->callbackRegistry->jobs[i].user_data)
- PyTuple_SetItem(job, 3, GContext->callbackRegistry->jobs[i].user_data); // steals data, so don't deref
- else
- PyTuple_SetItem(job, 3, GetPyNone());
+ // app_data_func() returns a new PyObject reference (passing ownership to us),
+ // therefore we don't need to INCREF it.
+ PyObject* app_data = cur_entry.app_data_func();
+ PyTuple_SetItem(job, 2, app_data? app_data : GetPyNone());
+
+ PyObject* user_data = *cur_entry.user_data;
+ Py_XINCREF(user_data);
+ PyTuple_SetItem(job, 3, user_data? user_data : GetPyNone());
PyTuple_SetItem(pArgs, i, job);
}
diff --git a/src/mvAppItem.cpp b/src/mvAppItem.cpp
index b5f393057..b5fdffbf6 100644
--- a/src/mvAppItem.cpp
+++ b/src/mvAppItem.cpp
@@ -36,12 +36,6 @@ mvAppItem::~mvAppItem()
if (type == mvAppItemType::mvTable)
static_cast(this)->onChildrenRemoved();
- mvGlobalIntepreterLock gil;
- if (config.callback) Py_DECREF(config.callback);
- if (config.user_data) Py_DECREF(config.user_data);
- if (config.dragCallback) Py_DECREF(config.dragCallback);
- if (config.dropCallback) Py_DECREF(config.dropCallback);
-
// in case item registry is destroyed
if (GContext->itemRegistry)
{
@@ -54,13 +48,87 @@ mvAppItem::~mvAppItem()
}
}
-PyObject*
-mvAppItem::getCallback(bool ignore_enabled)
+void mvAppItem::submitCallback()
+{
+ submitCallbackEx([]() -> PyObject* { return nullptr; });
+}
+
+template<>
+void mvAppItem::submitCallback(std::string app_data)
+{
+ submitCallbackEx([app_data=std::move(app_data)]() { return ToPyString(app_data); });
+}
+
+template<>
+void mvAppItem::submitCallback(bool app_data)
+{
+ submitCallbackEx([=]() { return ToPyBool(app_data); });
+}
+
+template<>
+void mvAppItem::submitCallback(float app_data)
+{
+ submitCallbackEx([=]() { return ToPyFloat(app_data); });
+}
+
+template<>
+void mvAppItem::submitCallback(double app_data)
+{
+ submitCallbackEx([=]() { return ToPyDouble(app_data); });
+}
+
+template<>
+void mvAppItem::submitCallback(int app_data)
+{
+ submitCallbackEx([=]() { return ToPyInt(app_data); });
+}
+
+template<>
+void mvAppItem::submitCallback(std::array app_data)
+{
+ submitCallbackEx([app_data=std::move(app_data)]() { return ToPyIntList(app_data.data(), (int) app_data.size()); });
+}
+
+template<>
+void mvAppItem::submitCallback(std::array app_data)
{
- if (config.enabled)
- return config.callback;
+ submitCallbackEx([app_data=std::move(app_data)]() { return ToPyFloatList(app_data.data(), (int) app_data.size()); });
+}
- return ignore_enabled ? config.callback : nullptr;
+template<>
+void mvAppItem::submitCallback(std::array app_data)
+{
+ submitCallbackEx([app_data=std::move(app_data)]() { return ToPyFloatList(app_data.data(), (int) app_data.size()); });
+}
+
+template<>
+void mvAppItem::submitCallback(mvColor app_data)
+{
+ submitCallbackEx([=]() { return ToPyColor(app_data); });
+}
+
+template<>
+void mvAppItem::submitCallback(mvUUID app_data)
+{
+ submitCallbackEx([=]() { return ToPyUUID(app_data); });
+}
+
+template<>
+void mvAppItem::submitCallback(tm app_data)
+{
+ submitCallbackEx([=]() { return ToPyTime(app_data); });
+}
+
+template<>
+void mvAppItem::submitCallback(ImVec2 app_data)
+{
+ submitCallbackEx([=]() { return ToPyPair(app_data.x, app_data.y); });
+}
+
+template<>
+void mvAppItem::submitCallback(ImGuiKey app_data)
+{
+ submitCallbackEx([=]() { return ToPyInt(app_data); });
}
void
@@ -163,52 +231,22 @@ mvAppItem::handleKeywordArgs(PyObject* dict, const std::string& parser)
if (PyObject* item = PyDict_GetItemString(dict, "callback"))
{
- if (config.callback)
- Py_XDECREF(config.callback);
-
- // TODO: investigate if PyNone should be increffed
- Py_XINCREF(item);
- if (item == Py_None)
- config.callback = nullptr;
- else
- config.callback = item;
+ config.callback = mvPyObject(item == Py_None? nullptr : item, true);
}
if (PyObject* item = PyDict_GetItemString(dict, "drag_callback"))
{
- if (config.dragCallback)
- Py_XDECREF(config.dragCallback);
-
- Py_XINCREF(item);
- if (item == Py_None)
- config.dragCallback = nullptr;
- else
- config.dragCallback = item;
+ config.dragCallback = mvPyObject(item == Py_None? nullptr : item, true);
}
if (PyObject* item = PyDict_GetItemString(dict, "drop_callback"))
{
- if (config.dropCallback)
- Py_XDECREF(config.dropCallback);
-
- Py_XINCREF(item);
-
- if (item == Py_None)
- config.dropCallback = nullptr;
- else
- config.dropCallback = item;
+ config.dropCallback = mvPyObject(item == Py_None? nullptr : item, true);
}
if (PyObject* item = PyDict_GetItemString(dict, "user_data"))
{
- if (config.user_data)
- Py_XDECREF(config.user_data);
-
- Py_XINCREF(item);
- if (item == Py_None)
- config.user_data = nullptr;
- else
- config.user_data = item;
+ *config.user_data = mvPyObject(item == Py_None? nullptr : item, true);
}
handleSpecificKeywordArgs(dict);
diff --git a/src/mvAppItem.h b/src/mvAppItem.h
index b8711a95d..a0e3a0f0e 100644
--- a/src/mvAppItem.h
+++ b/src/mvAppItem.h
@@ -127,10 +127,13 @@ struct mvAppItemConfig
bool searchDelayed = false;
bool useInternalLabel = true; // when false, will use specificed label
bool tracked = false;
- PyObject* callback = nullptr;
- PyObject* user_data = nullptr;
- PyObject* dragCallback = nullptr;
- PyObject* dropCallback = nullptr;
+ mvPyObject callback = nullptr;
+ mvPyObject dragCallback = nullptr;
+ mvPyObject dropCallback = nullptr;
+ // We store user_data as a pointer because that's how we'll need it when submitting
+ // the callback. This is to pass user_data into mvAddCallback that comes from a
+ // different source than the callback owner (required for the drag callback).
+ std::shared_ptr user_data = std::make_shared(nullptr);
};
struct mvAppItemDrawInfo
@@ -146,7 +149,7 @@ struct mvAppItemDrawInfo
//-----------------------------------------------------------------------------
// mvAppItem
//-----------------------------------------------------------------------------
-class mvAppItem
+class mvAppItem : public std::enable_shared_from_this
{
public:
@@ -207,7 +210,35 @@ class mvAppItem
//-----------------------------------------------------------------------------
// callbacks
//-----------------------------------------------------------------------------
- [[nodiscard]] PyObject* getCallback(b8 ignore_enabled = true); // returns the callback. If ignore_enable false and item is disabled then no callback will be returned.
+ // Submits the specified callback, if any, with user_data from the item config
+ // and app_data created by app_data_func (typically a lambda).
+ // Note: `callback` must be a member of `this` (or of a nested object, like `config`).
+ // It does *not* check if the item is enabled or disabled, because some "custom"
+ // callbacks historically run even on disabled items.
+ template
+ void submitCallbackEx(PyObject* callback, AppDataFunc app_data_func)
+ {
+ // The current `mvAppItem` becomes the owner of this callback, and as soon
+ // as it gets deleted, the callback entry will be thrown away.
+ mvAddCallback(weak_from_this(), callback, config.user_data, uuid, config.alias, app_data_func);
+ }
+
+ // Submits the mvAppItem's "default" callback, if any, with user_data from
+ // the item config and app_data created by app_data_func (typically a lambda).
+ // The callback is only submitted if the item is enabled; on a disabled item,
+ // the call is effectively ignored.
+ template
+ void submitCallbackEx(AppDataFunc app_data_func)
+ {
+ if (!config.enabled)
+ return;
+ submitCallbackEx(config.callback, app_data_func);
+ }
+
+ template
+ void submitCallback(AppDataType app_data); // submits the callback, if any, passing it app_data, and also user_data from the item config
+
+ void submitCallback(); // submits the callback with app_data=None
//-----------------------------------------------------------------------------
// config setters
diff --git a/src/mvBasicWidgets.cpp b/src/mvBasicWidgets.cpp
index f8c48244c..7c009d96e 100644
--- a/src/mvBasicWidgets.cpp
+++ b/src/mvBasicWidgets.cpp
@@ -2749,10 +2749,7 @@ DearPyGui::draw_button(ImDrawList* drawlist, mvAppItem& item, const mvButtonConf
if (activated)
{
- if (item.config.alias.empty())
- mvAddCallback(item.getCallback(false), item.uuid, nullptr, item.config.user_data);
- else
- mvAddCallback(item.getCallback(false), item.config.alias, nullptr, item.config.user_data);
+ item.submitCallback();
}
}
@@ -2857,12 +2854,7 @@ DearPyGui::draw_combo(ImDrawList* drawlist, mvAppItem& item, mvComboConfig& conf
{
if (item.config.enabled) { *config.value = name; }
- auto value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {mvAddCallback(item.getCallback(false), item.uuid, ToPyString(value), item.config.user_data);});
- else
- mvSubmitCallback([&item, value]() { mvAddCallback(item.getCallback(false), item.config.alias, ToPyString(value), item.config.user_data);});
+ item.submitCallback(*config.value);
}
item.state.edited = ImGui::IsItemEdited();
@@ -2959,12 +2951,7 @@ DearPyGui::draw_checkbox(ImDrawList* drawlist, mvAppItem& item, mvCheckboxConfig
if (ImGui::Checkbox(item.info.internalLabel.c_str(), item.config.enabled ? config.value.get() : &config.disabled_value))
{
- bool value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() { mvAddCallback(item.getCallback(false), item.uuid, ToPyBool(value), item.config.user_data);});
- else
- mvSubmitCallback([&item, value]() { mvAddCallback(item.getCallback(false), item.config.alias, ToPyBool(value), item.config.user_data);});
+ item.submitCallback(*config.value);
}
}
@@ -3056,12 +3043,7 @@ DearPyGui::draw_drag_float(ImDrawList* drawlist, mvAppItem& item, mvDragFloatCon
item.config.enabled ? config.value.get() : &config.disabled_value,
config.speed, config.minv, config.maxv, config.format.c_str(), config.flags))
{
- auto value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {mvAddCallback(item.getCallback(false), item.uuid, ToPyFloat(value), item.config.user_data);});
- else
- mvSubmitCallback([&item, value]() { mvAddCallback(item.getCallback(false), item.config.alias, ToPyFloat(value), item.config.user_data);});
+ item.submitCallback(*config.value);
}
}
@@ -3154,12 +3136,7 @@ DearPyGui::draw_drag_double(ImDrawList* drawlist, mvAppItem& item, mvDragDoubleC
item.config.enabled ? config.value.get() : &config.disabled_value,
config.speed, &config.minv, &config.maxv, config.format.c_str(), config.flags))
{
- auto value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {mvAddCallback(item.getCallback(false), item.uuid, ToPyDouble(value), item.config.user_data); });
- else
- mvSubmitCallback([&item, value]() { mvAddCallback(item.getCallback(false), item.config.alias, ToPyDouble(value), item.config.user_data); });
+ item.submitCallback(*config.value);
}
}
@@ -3252,15 +3229,7 @@ DearPyGui::draw_drag_int(ImDrawList* drawlist, mvAppItem& item, mvDragIntConfig&
item.config.enabled ? config.value.get() : &config.disabled_value, config.speed,
config.minv, config.maxv, config.format.c_str(), config.flags))
{
- auto value = *config.value;
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.uuid, ToPyInt(value), item.config.user_data);
- });
- else
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.config.alias, ToPyInt(value), item.config.user_data);
- });
+ item.submitCallback(*config.value);
}
}
@@ -3369,15 +3338,7 @@ DearPyGui::draw_drag_intx(ImDrawList* drawlist, mvAppItem& item, mvDragIntMultiC
if (activated)
{
- auto value = *config.value;
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.uuid, ToPyIntList(value.data(), (int)value.size()), item.config.user_data);
- });
- else
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.config.alias, ToPyIntList(value.data(), (int)value.size()), item.config.user_data);
- });
+ item.submitCallback(*config.value);
}
}
@@ -3484,15 +3445,7 @@ DearPyGui::draw_drag_floatx(ImDrawList* drawlist, mvAppItem& item, mvDragFloatMu
if (activated)
{
- auto value = *config.value;
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.uuid, ToPyFloatList(value.data(), (int)value.size()), item.config.user_data);
- });
- else
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.config.alias, ToPyFloatList(value.data(), (int)value.size()), item.config.user_data);
- });
+ item.submitCallback(*config.value);
}
}
//-----------------------------------------------------------------------------
@@ -3586,15 +3539,7 @@ DearPyGui::draw_drag_doublex(ImDrawList* drawlist, mvAppItem& item, mvDragDouble
if (activated)
{
- auto value = *config.value;
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.uuid, ToPyFloatList(value.data(), (int)value.size()), item.config.user_data);
- });
- else
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.config.alias, ToPyFloatList(value.data(), (int)value.size()), item.config.user_data);
- });
+ item.submitCallback(*config.value);
}
}
//-----------------------------------------------------------------------------
@@ -3690,12 +3635,7 @@ DearPyGui::draw_slider_float(ImDrawList* drawlist, mvAppItem& item, mvSliderFloa
if (ImGui::VSliderFloat(item.info.internalLabel.c_str(), ImVec2((float)item.config.width, (float)item.config.height), item.config.enabled ? config.value.get() : &config.disabled_value, config.minv, config.maxv, config.format.c_str()))
{
- auto value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() { mvAddCallback(item.getCallback(false), item.uuid, ToPyFloat(value), item.config.user_data);});
- else
- mvSubmitCallback([&item, value]() { mvAddCallback(item.getCallback(false), item.config.alias, ToPyFloat(value), item.config.user_data);});
+ item.submitCallback(*config.value);
}
}
@@ -3703,11 +3643,7 @@ DearPyGui::draw_slider_float(ImDrawList* drawlist, mvAppItem& item, mvSliderFloa
{
if (ImGui::SliderFloat(item.info.internalLabel.c_str(), item.config.enabled ? config.value.get() : &config.disabled_value, config.minv, config.maxv, config.format.c_str(), config.flags))
{
- auto value = *config.value;
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {mvAddCallback(item.getCallback(false), item.uuid, ToPyFloat(value), item.config.user_data);});
- else
- mvSubmitCallback([&item, value]() {mvAddCallback(item.getCallback(false), item.config.alias, ToPyFloat(value), item.config.user_data);});
+ item.submitCallback(*config.value);
}
}
@@ -3806,12 +3742,7 @@ DearPyGui::draw_slider_double(ImDrawList* drawlist, mvAppItem& item, mvSliderDou
if (ImGui::VSliderScalar(item.info.internalLabel.c_str(), ImVec2((float)item.config.width, (float)item.config.height), ImGuiDataType_Double, item.config.enabled ? config.value.get() : &config.disabled_value, &config.minv, &config.maxv, config.format.c_str()))
{
- auto value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() { mvAddCallback(item.getCallback(false), item.uuid, ToPyDouble(value), item.config.user_data); });
- else
- mvSubmitCallback([&item, value]() { mvAddCallback(item.getCallback(false), item.config.alias, ToPyDouble(value), item.config.user_data); });
+ item.submitCallback(*config.value);
}
}
@@ -3819,11 +3750,7 @@ DearPyGui::draw_slider_double(ImDrawList* drawlist, mvAppItem& item, mvSliderDou
{
if (ImGui::SliderScalar(item.info.internalLabel.c_str(), ImGuiDataType_Double, item.config.enabled ? config.value.get() : &config.disabled_value, &config.minv, &config.maxv, config.format.c_str(), config.flags))
{
- auto value = *config.value;
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {mvAddCallback(item.getCallback(false), item.uuid, ToPyDouble(value), item.config.user_data); });
- else
- mvSubmitCallback([&item, value]() {mvAddCallback(item.getCallback(false), item.config.alias, ToPyDouble(value), item.config.user_data); });
+ item.submitCallback(*config.value);
}
}
@@ -3932,12 +3859,7 @@ DearPyGui::draw_slider_floatx(ImDrawList* drawlist, mvAppItem& item, mvSliderFlo
if (activated)
{
- auto value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {mvAddCallback(item.getCallback(false), item.uuid, ToPyFloatList(value.data(), (int)value.size()), item.config.user_data);});
- else
- mvSubmitCallback([&item, value]() {mvAddCallback(item.getCallback(false), item.config.alias, ToPyFloatList(value.data(), (int)value.size()), item.config.user_data);});
+ item.submitCallback(*config.value);
}
}
@@ -4032,12 +3954,7 @@ DearPyGui::draw_slider_doublex(ImDrawList* drawlist, mvAppItem& item, mvSliderDo
if (activated)
{
- auto value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {mvAddCallback(item.getCallback(false), item.uuid, ToPyFloatList(value.data(), (int)value.size()), item.config.user_data); });
- else
- mvSubmitCallback([&item, value]() {mvAddCallback(item.getCallback(false), item.config.alias, ToPyFloatList(value.data(), (int)value.size()), item.config.user_data); });
+ item.submitCallback(*config.value);
}
}
@@ -4134,12 +4051,7 @@ DearPyGui::draw_slider_int(ImDrawList* drawlist, mvAppItem& item, mvSliderIntCon
if (ImGui::VSliderInt(item.info.internalLabel.c_str(), ImVec2((float)item.config.width, (float)item.config.height), item.config.enabled ? config.value.get() : &config.disabled_value, config.minv, config.maxv, config.format.c_str()))
{
- auto value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() { mvAddCallback(item.getCallback(false), item.uuid, ToPyInt(value), item.config.user_data);});
- else
- mvSubmitCallback([&item, value]() { mvAddCallback(item.getCallback(false), item.config.alias, ToPyInt(value), item.config.user_data);});
+ item.submitCallback(*config.value);
}
}
@@ -4147,11 +4059,7 @@ DearPyGui::draw_slider_int(ImDrawList* drawlist, mvAppItem& item, mvSliderIntCon
{
if (ImGui::SliderInt(item.info.internalLabel.c_str(), item.config.enabled ? config.value.get() : &config.disabled_value, config.minv, config.maxv, config.format.c_str(), config.flags))
{
- auto value = *config.value;
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {mvAddCallback(item.getCallback(false), item.uuid, ToPyInt(value), item.config.user_data);});
- else
- mvSubmitCallback([&item, value]() {mvAddCallback(item.getCallback(false), item.config.alias, ToPyInt(value), item.config.user_data);});
+ item.submitCallback(*config.value);
}
}
@@ -4260,12 +4168,7 @@ DearPyGui::draw_slider_intx(ImDrawList* drawlist, mvAppItem& item, mvSliderIntMu
if (activated)
{
- auto value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {mvAddCallback(item.getCallback(false), item.uuid, ToPyIntList(value.data(), (int)value.size()), item.config.user_data); });
- else
- mvSubmitCallback([&item, value]() {mvAddCallback(item.getCallback(false), item.config.alias, ToPyIntList(value.data(), (int)value.size()), item.config.user_data); });
+ item.submitCallback(*config.value);
}
}
@@ -4366,16 +4269,7 @@ DearPyGui::draw_listbox(ImDrawList *drawlist, mvAppItem &item, mvListboxConfig &
{
*config.value = config.names[config.index];
config.disabled_value = config.names[config.index];
- auto value = *config.value;
-
- if(item.config.alias.empty())
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.uuid, ToPyString(value), item.config.user_data);
- });
- else
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.config.alias, ToPyString(value), item.config.user_data);
- });
+ item.submitCallback(*config.value);
}
ImGui::PopStyleColor();
@@ -4481,12 +4375,7 @@ DearPyGui::draw_radio_button(ImDrawList* drawlist, mvAppItem& item, mvRadioButto
{
*config.value = config.itemnames[config.index];
config.disabled_value = config.itemnames[config.index];
- auto value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {mvAddCallback(item.getCallback(false), item.uuid, ToPyString(value), item.config.user_data);});
- else
- mvSubmitCallback([&item, value]() { mvAddCallback(item.getCallback(false), item.config.alias, ToPyString(value), item.config.user_data);});
+ item.submitCallback(*config.value);
}
item.state.edited = ImGui::IsItemEdited();
@@ -4615,15 +4504,7 @@ DearPyGui::draw_input_text(ImDrawList* drawlist, mvAppItem& item, mvInputTextCon
if (activated)
{
- auto value = *config.value;
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.uuid, ToPyString(value), item.config.user_data);
- });
- else
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.config.alias, ToPyString(value), item.config.user_data);
- });
+ item.submitCallback(*config.value);
}
}
@@ -4735,16 +4616,7 @@ DearPyGui::draw_input_int(ImDrawList* drawlist, mvAppItem& item, mvInputIntConfi
if (config.last_value != *config.value)
{
config.last_value = *config.value;
- auto value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.uuid, ToPyInt(value), item.config.user_data);
- });
- else
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.config.alias, ToPyInt(value), item.config.user_data);
- });
+ item.submitCallback(*config.value);
}
}
@@ -4884,16 +4756,7 @@ DearPyGui::draw_input_floatx(ImDrawList* drawlist, mvAppItem& item, mvInputFloat
if (config.last_value != *config.value)
{
config.last_value = *config.value;
- auto value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.uuid, ToPyFloatList(value.data(), (int)value.size()), item.config.user_data);
- });
- else
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.config.alias, ToPyFloatList(value.data(), (int)value.size()), item.config.user_data);
- });
+ item.submitCallback(*config.value);
}
}
@@ -5007,16 +4870,7 @@ DearPyGui::draw_input_float(ImDrawList* drawlist, mvAppItem& item, mvInputFloatC
if (config.last_value != *config.value)
{
config.last_value = *config.value;
- auto value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.uuid, ToPyFloat(value), item.config.user_data);
- });
- else
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.config.alias, ToPyFloat(value), item.config.user_data);
- });
+ item.submitCallback(*config.value);
}
}
}
@@ -5108,13 +4962,7 @@ DearPyGui::draw_knob_float(ImDrawList* drawlist, mvAppItem& item, mvKnobFloatCon
if (KnobFloat(item.config.specifiedLabel.c_str(), item.config.enabled ? config.value.get() : &config.disabled_value, config.minv, config.maxv, config.step))
{
- auto value = *config.value;
- mvSubmitCallback([&item, value]() {
- if (item.config.alias.empty())
- mvAddCallback(item.getCallback(false), item.uuid, ToPyFloat(value), item.config.user_data);
- else
- mvAddCallback(item.getCallback(false), item.config.alias, ToPyFloat(value), item.config.user_data);
- });
+ item.submitCallback(*config.value);
}
}
@@ -5225,16 +5073,7 @@ DearPyGui::draw_input_double(ImDrawList* drawlist, mvAppItem& item, mvInputDoubl
if (config.last_value != *config.value)
{
config.last_value = *config.value;
- auto value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.uuid, ToPyDouble(value), item.config.user_data);
- });
- else
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.config.alias, ToPyDouble(value), item.config.user_data);
- });
+ item.submitCallback(*config.value);
}
}
}
@@ -5361,16 +5200,7 @@ DearPyGui::draw_input_doublex(ImDrawList* drawlist, mvAppItem& item, mvInputDoub
if (config.last_value != *config.value)
{
config.last_value = *config.value;
- auto value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.uuid, ToPyFloatList(value.data(), (int)value.size()), item.config.user_data);
- });
- else
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.config.alias, ToPyFloatList(value.data(), (int)value.size()), item.config.user_data);
- });
+ item.submitCallback(*config.value);
}
}
@@ -5511,16 +5341,7 @@ DearPyGui::draw_input_intx(ImDrawList* drawlist, mvAppItem& item, mvInputIntMult
{
config.last_value = *config.value;
- auto value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.uuid, ToPyIntList(value.data(), (int)value.size()), item.config.user_data);
- });
- else
- mvSubmitCallback([&item, value]() {
- mvAddCallback(item.getCallback(false), item.config.alias, ToPyIntList(value.data(), (int)value.size()), item.config.user_data);
- });
+ item.submitCallback(*config.value);
}
}
}
@@ -5743,12 +5564,7 @@ DearPyGui::draw_selectable(ImDrawList* drawlist, mvAppItem& item, mvSelectableCo
if (ImGui::Selectable(item.info.internalLabel.c_str(), config.value.get(), config.flags, ImVec2((float)item.config.width, (float)item.config.height)))
{
- auto value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() { mvAddCallback(item.getCallback(false), item.uuid, ToPyBool(value), item.config.user_data);});
- else
- mvSubmitCallback([&item, value]() {mvAddCallback(item.getCallback(false), item.config.alias, ToPyBool(value), item.config.user_data);});
+ item.submitCallback(*config.value);
}
}
@@ -5836,10 +5652,7 @@ DearPyGui::draw_tab_button(ImDrawList* drawlist, mvAppItem& item, mvTabButtonCon
if (ImGui::TabItemButton(item.info.internalLabel.c_str(), config.flags))
{
- if (item.config.alias.empty())
- mvAddCallback(item.getCallback(false), item.uuid, nullptr, item.config.user_data);
- else
- mvAddCallback(item.getCallback(false), item.config.alias, nullptr, item.config.user_data);
+ item.submitCallback();
}
}
@@ -5934,12 +5747,7 @@ DearPyGui::draw_menu_item(ImDrawList* drawlist, mvAppItem& item, mvMenuItemConfi
// create menu item and see if its selected
if (ImGui::MenuItem(item.info.internalLabel.c_str(), config.shortcut.c_str(), config.check ? config.value.get() : nullptr, item.config.enabled))
{
- bool value = *config.value;
-
- if (item.config.alias.empty())
- mvAddCallback(item.config.callback, item.uuid, ToPyBool(value), item.config.user_data);
- else
- mvAddCallback(item.config.callback, item.config.alias, ToPyBool(value), item.config.user_data);
+ item.submitCallback(*config.value);
}
ImGui::PopStyleColor();
@@ -6259,10 +6067,7 @@ DearPyGui::draw_image_button(ImDrawList* drawlist, mvAppItem& item, mvImageButto
ImVec2(config.uv_min.x, config.uv_min.y), ImVec2(config.uv_max.x, config.uv_max.y),
config.backgroundColor, config.tintColor))
{
- if (item.config.alias.empty())
- mvAddCallback(item.getCallback(false), item.uuid, nullptr, item.config.user_data);
- else
- mvAddCallback(item.getCallback(false), item.config.alias, nullptr, item.config.user_data);
+ item.submitCallback();
}
ImGui::PopID();
diff --git a/src/mvCallbackRegistry.cpp b/src/mvCallbackRegistry.cpp
index 079b1a338..d292263dd 100644
--- a/src/mvCallbackRegistry.cpp
+++ b/src/mvCallbackRegistry.cpp
@@ -19,15 +19,27 @@ void mvRunTasks()
void mvFrameCallback(i32 frame)
{
+ auto callbackRegistry = GContext->callbackRegistry; // for brevity
- if (frame > GContext->callbackRegistry->highestFrame)
+ if (frame > callbackRegistry->highestFrame)
return;
- if (GContext->callbackRegistry->frameCallbacks.count(frame) == 0)
+ if (callbackRegistry->frameCallbacks.count(frame) == 0)
return;
- mvAddCallback(GContext->callbackRegistry->frameCallbacks[frame], frame, nullptr,
- GContext->callbackRegistry->frameCallbacksUserData[frame]);
+ // We have to use `std::unordered_map::at()` instead of indexing it with `[]`:
+ // `mvPyObject` has no default constructor, whereas `operator[]` can insert
+ // a new value into the map and therefore requires a default constructor.
+
+ auto callback = std::make_shared(std::move(callbackRegistry->frameCallbacks.at(frame)));
+ auto user_data = std::make_shared(std::move(callbackRegistry->frameCallbacksUserData.at(frame)));
+
+ // Since mvPyObject objects remaining in the maps are now "empty", it's safe to
+ // delete them right here even though we don't own the GIL.
+ callbackRegistry->frameCallbacks.erase(frame);
+ callbackRegistry->frameCallbacksUserData.erase(frame);
+
+ mvAddOwnerlessCallback(callback, user_data, (mvUUID)frame, "", []() -> PyObject* { return nullptr; });
}
bool mvRunCallbacks()
@@ -49,314 +61,92 @@ bool mvRunCallbacks()
return true;
}
-void mvAddCallback(PyObject* callable, mvUUID sender, PyObject* app_data, PyObject* user_data, bool decrementAppData)
+void mvAddCallback(const std::weak_ptr& owner,
+ PyObject* callback,
+ const std::shared_ptr& user_data,
+ mvUUID sender,
+ const std::string& alias)
{
-
- if (GContext->callbackRegistry->callCount > GContext->callbackRegistry->maxNumberOfCalls)
- {
- if (app_data != nullptr)
- Py_XDECREF(app_data);
- if (user_data != nullptr)
- Py_XDECREF(user_data);
- assert(false);
- return;
- }
-
- if (GContext->IO.manualCallbacks)
- {
- if (callable != nullptr)
- Py_XINCREF(callable);
- if (app_data != nullptr)
- Py_XINCREF(app_data);
- if (user_data != nullptr)
- Py_XINCREF(user_data);
- GContext->callbackRegistry->jobs.push_back({ sender, callable, app_data, user_data });
- return;
- }
-
- mvSubmitCallback([=]() {
- mvRunCallback(callable, sender, app_data, user_data, decrementAppData);
- });
+ mvAddCallback(owner, callback, user_data, sender, alias, []() -> PyObject* { return nullptr; });
}
-void mvAddCallback(PyObject* callable, const std::string& sender, PyObject* app_data, PyObject* user_data)
+void mvAddOwnerlessCallback(const std::shared_ptr& callback,
+ const std::shared_ptr& user_data,
+ mvUUID sender,
+ const std::string& alias)
{
-
- if (GContext->callbackRegistry->callCount > GContext->callbackRegistry->maxNumberOfCalls)
- {
-
- if (app_data != nullptr)
- Py_XDECREF(app_data);
- if (user_data != nullptr)
- Py_XDECREF(user_data);
- assert(false);
- return;
- }
-
- if (GContext->IO.manualCallbacks)
- {
- if (callable != nullptr)
- Py_XINCREF(callable);
- if (app_data != nullptr)
- Py_XINCREF(app_data);
- if (user_data != nullptr)
- Py_XINCREF(user_data);
- GContext->callbackRegistry->jobs.push_back({ 0, callable, app_data, user_data, sender });
- return;
- }
-
- mvSubmitCallback([=]() {
- mvRunCallback(callable, sender, app_data, user_data);
- });
+ mvAddOwnerlessCallback(callback, user_data, sender, alias, []() -> PyObject* { return nullptr; });
}
-void mvRunCallback(PyObject* callable, const std::string& sender, PyObject* app_data, PyObject* user_data)
+void mvRunCallback(PyObject* callback, PyObject* user_data, mvUUID sender, const std::string& sender_alias, PyObject* app_data)
{
- if (callable == nullptr)
- {
- //if (data != nullptr)
- // Py_XDECREF(data);
+ if (callback == nullptr)
return;
- }
- if (!PyCallable_Check(callable))
+ if (!PyCallable_Check(callback))
{
- if (app_data != nullptr)
- Py_XDECREF(app_data);
- if (user_data != nullptr)
- Py_XDECREF(user_data);
mvThrowPythonError(mvErrorCode::mvNone, "Callable not callable.");
PyErr_Print();
return;
}
- if (app_data == nullptr)
- {
- app_data = Py_None;
- Py_XINCREF(app_data);
- }
- Py_XINCREF(app_data);
-
- if (user_data == nullptr)
- {
- user_data = Py_None;
- Py_XINCREF(user_data);
- }
- Py_XINCREF(user_data);
-
//PyErr_Clear();
if (PyErr_Occurred())
PyErr_Print();
- if (PyErr_Occurred())
- PyErr_Print();
-
- PyObject* fc = PyObject_GetAttrString(callable, "__code__");
+ PyObject* fc = PyObject_GetAttrString(callback, "__code__");
if (fc) {
PyObject* ac = PyObject_GetAttrString(fc, "co_argcount");
if (ac) {
i32 count = PyLong_AsLong(ac);
- if (PyMethod_Check(callable))
+ if (PyMethod_Check(callback))
count--;
- if (count > 3)
- {
- mvPyObject pArgs(PyTuple_New(count));
- PyTuple_SetItem(pArgs, 0, ToPyString(sender));
- PyTuple_SetItem(pArgs, 1, app_data); // steals data, so don't deref
- PyTuple_SetItem(pArgs, 2, user_data); // steals data, so don't deref
-
- for (int i = 3; i < count; i++)
- PyTuple_SetItem(pArgs, i, GetPyNone());
-
- mvPyObject result(PyObject_CallObject(callable, pArgs));
-
- // check if call succeeded
- if (!result.isOk())
- PyErr_Print();
-
- }
- else if (count == 3)
- {
- mvPyObject pArgs(PyTuple_New(3));
- PyTuple_SetItem(pArgs, 0, ToPyString(sender));
- PyTuple_SetItem(pArgs, 1, app_data); // steals data, so don't deref
- PyTuple_SetItem(pArgs, 2, user_data); // steals data, so don't deref
-
- mvPyObject result(PyObject_CallObject(callable, pArgs));
-
- pArgs.delRef();
- // check if call succeeded
- if (!result.isOk())
- PyErr_Print();
-
- }
- else if (count == 2)
- {
- mvPyObject pArgs(PyTuple_New(2));
- PyTuple_SetItem(pArgs, 0, ToPyString(sender));
- PyTuple_SetItem(pArgs, 1, app_data); // steals data, so don't deref
-
- mvPyObject result(PyObject_CallObject(callable, pArgs));
-
- pArgs.delRef();
- // check if call succeeded
- if (!result.isOk())
- PyErr_Print();
+ mvPyObject pArgs(count > 0 ? PyTuple_New(count) : nullptr);
- }
- else if (count == 1)
+ if (count > 0)
{
- mvPyObject pArgs(PyTuple_New(1));
- PyTuple_SetItem(pArgs, 0, ToPyString(sender));
-
- mvPyObject result(PyObject_CallObject(callable, pArgs));
-
- // check if call succeeded
- if (!result.isOk())
- PyErr_Print();
+ PyTuple_SetItem(pArgs, 0, sender_alias.empty()?
+ ToPyUUID(sender) :
+ ToPyString(sender_alias));
+
+ if (count > 1)
+ {
+ if (app_data == nullptr)
+ app_data = Py_None;
+ // Need an owned ref here: PyTuple_SetItem takes ownership;
+ // this also handles Py_None correctly (need to incref it).
+ Py_INCREF(app_data);
+ PyTuple_SetItem(pArgs, 1, app_data);
+
+ if (count > 2)
+ {
+ if (user_data == nullptr)
+ user_data = Py_None;
+ // Need an owned ref here: PyTuple_SetItem takes ownership;
+ // this also handles Py_None correctly (need to incref it).
+ Py_INCREF(user_data);
+ PyTuple_SetItem(pArgs, 2, user_data);
+
+ // If the callback takes more parms, just pass None in there
+ for (int i = 3; i < count; i++)
+ PyTuple_SetItem(pArgs, i, GetPyNone());
+ }
+ }
}
- else
- {
- mvPyObject result(PyObject_CallObject(callable, nullptr));
- // check if call succeeded
- if (!result.isOk())
- PyErr_Print();
+ // perform the actual call
+ mvPyObject result(PyObject_CallObject(callback, pArgs));
+ // check if call succeeded
+ if (!result.isOk())
+ PyErr_Print();
- }
Py_DECREF(ac);
}
Py_DECREF(fc);
}
}
-
-void mvRunCallback(PyObject* callable, mvUUID sender, PyObject* app_data, PyObject* user_data, bool decrementAppData)
-{
-
- if (callable == nullptr)
- {
- //if (data != nullptr)
- // Py_XDECREF(data);
- return;
- }
-
- if (!PyCallable_Check(callable))
- {
- if (app_data != nullptr)
- Py_XDECREF(app_data);
- if (user_data != nullptr)
- Py_XDECREF(user_data);
- mvThrowPythonError(mvErrorCode::mvNone, "Callable not callable.");
- PyErr_Print();
- return;
- }
-
- if (app_data == nullptr)
- {
- app_data = Py_None;
- Py_XINCREF(app_data);
- }
- if(decrementAppData)
- Py_XINCREF(app_data);
-
- if (user_data == nullptr)
- {
- user_data = Py_None;
- Py_XINCREF(user_data);
- }
- Py_XINCREF(user_data);
-
- //PyErr_Clear();
- if (PyErr_Occurred())
- PyErr_Print();
-
- if (PyErr_Occurred())
- PyErr_Print();
-
- PyObject* fc = PyObject_GetAttrString(callable, "__code__");
- if (fc) {
- PyObject* ac = PyObject_GetAttrString(fc, "co_argcount");
- if (ac) {
- i32 count = PyLong_AsLong(ac);
-
- if (PyMethod_Check(callable))
- count--;
-
- if (count > 3)
- {
- mvPyObject pArgs(PyTuple_New(count));
- PyTuple_SetItem(pArgs, 0, ToPyUUID(sender));
- PyTuple_SetItem(pArgs, 1, app_data); // steals data, so don't deref
- PyTuple_SetItem(pArgs, 2, user_data); // steals data, so don't deref
-
- for (int i = 3; i < count; i++)
- PyTuple_SetItem(pArgs, i, GetPyNone());
-
- mvPyObject result(PyObject_CallObject(callable, pArgs));
-
- // check if call succeeded
- if (!result.isOk())
- PyErr_Print();
-
- }
- else if (count == 3)
- {
- mvPyObject pArgs(PyTuple_New(3));
- PyTuple_SetItem(pArgs, 0, ToPyUUID(sender));
- PyTuple_SetItem(pArgs, 1, app_data); // steals data, so don't deref
- PyTuple_SetItem(pArgs, 2, user_data); // steals data, so don't deref
-
- mvPyObject result(PyObject_CallObject(callable, pArgs));
-
- pArgs.delRef();
- // check if call succeeded
- if (!result.isOk())
- PyErr_Print();
-
- }
- else if (count == 2)
- {
- mvPyObject pArgs(PyTuple_New(2));
- PyTuple_SetItem(pArgs, 0, ToPyUUID(sender));
- PyTuple_SetItem(pArgs, 1, app_data); // steals data, so don't deref
-
- mvPyObject result(PyObject_CallObject(callable, pArgs));
-
- pArgs.delRef();
- // check if call succeeded
- if (!result.isOk())
- PyErr_Print();
-
- }
- else if(count == 1)
- {
- mvPyObject pArgs(PyTuple_New(1));
- PyTuple_SetItem(pArgs, 0, ToPyUUID(sender));
-
- mvPyObject result(PyObject_CallObject(callable, pArgs));
-
- // check if call succeeded
- if (!result.isOk())
- PyErr_Print();
- }
- else
- {
- mvPyObject result(PyObject_CallObject(callable, nullptr));
-
- // check if call succeeded
- if (!result.isOk())
- PyErr_Print();
-
-
- }
- Py_DECREF(ac);
- }
- Py_DECREF(fc);
- }
-
-}
\ No newline at end of file
diff --git a/src/mvCallbackRegistry.h b/src/mvCallbackRegistry.h
index 1c84f78ae..e8825bbd5 100644
--- a/src/mvCallbackRegistry.h
+++ b/src/mvCallbackRegistry.h
@@ -193,26 +193,25 @@ class mvQueue
};
-static PyObject* SanitizeCallback(PyObject* callback)
-{
- if (callback == Py_None)
- return nullptr;
-
- return callback;
-}
-
struct mvCallbackJob
{
- mvUUID sender = 0;
- PyObject* callback = nullptr;
- PyObject* app_data = nullptr;
- PyObject* user_data = nullptr;
- std::string sender_str;
+ std::weak_ptr owner;
+ // Only valid if `owner` is alive; one must lock() the owner before accessing
+ // the callback.
+ PyObject* callback;
+ std::shared_ptr user_data;
+ mvUUID sender;
+ std::string alias;
+ std::function app_data_func;
+ // Either `callback` (and `owner`) or `ownerless_callback` must be set,
+ // but not both - otherwise one of them will be ignored.
+ std::shared_ptr ownerless_callback = nullptr;
};
struct mvCallbackRegistry
{
- const i32 maxNumberOfCalls = 50;
+ // TODO: ideally, it should be configurable (e.g. via configure_app)
+ const i32 maxNumberOfCalls = 500;
std::vector jobs;
mvQueue tasks;
@@ -221,22 +220,89 @@ struct mvCallbackRegistry
std::atomic callCount = 0;
// callbacks
- PyObject* resizeCallback = nullptr;
- PyObject* onCloseCallback = nullptr;
- PyObject* resizeCallbackUserData = nullptr;
- PyObject* onCloseCallbackUserData = nullptr;
+ std::shared_ptr resizeCallback = std::make_shared(nullptr);
+ std::shared_ptr resizeCallbackUserData = std::make_shared(nullptr);
+ std::shared_ptr onCloseCallback = std::make_shared(nullptr);
+ std::shared_ptr onCloseCallbackUserData = std::make_shared(nullptr);
i32 highestFrame = 0;
- std::unordered_map frameCallbacks;
- std::unordered_map frameCallbacksUserData;
+ std::unordered_map frameCallbacks;
+ std::unordered_map frameCallbacksUserData;
};
void mvFrameCallback(i32 frame);
void mvRunTasks();
-void mvRunCallback(PyObject* callback, mvUUID sender, PyObject* app_data, PyObject* user_data, bool decrementAppData = true);
-void mvRunCallback(PyObject* callback, const std::string& sender, PyObject* app_data, PyObject* user_data);
-void mvAddCallback(PyObject* callback, mvUUID sender, PyObject* app_data, PyObject* user_data, bool decrementAppData = true);
-void mvAddCallback(PyObject* callback, const std::string& sender, PyObject* app_data, PyObject* user_data);
+// All PyObject references here are borrowed references - caller must release them after this call
+void mvRunCallback(PyObject* callback, PyObject* user_data, mvUUID sender = 0, const std::string& sender_alias = "", PyObject* app_data = nullptr);
+
+// Note: We pass the `callback` and its `user_data` as two separate arguments (rather
+// than a single object) because, even though they only make sense together, `mvAppItem` may
+// combine the same `user_data` with different callbacks. We don't want to spread `user_data`
+// instances all across `mvAppItem`.
+// The `callback` must be valid all the time while `owner` is alive. This works for
+// the fields of `owner`; if `callback` is not a field of `owner`, the caller must make
+// sure that `callback`'s lifetime is at least as long as `owner`'s.
+// When the callback is about to be executed on the handlers thread, the callback queue
+// acquires a shared_ptr to `owner` and holds it while the callback is being executed.
+// If the `owner` is already lost by the moment the callback is fetched from the
+// queue, the callback will be silently ignored. This effectively cleans the queue
+// from irrelevant callbacks - lingering there after `mvAppItem` deletion and such.
+template
+void mvAddCallback(const std::weak_ptr& owner,
+ PyObject* callback,
+ const std::shared_ptr& user_data,
+ mvUUID sender,
+ const std::string& alias,
+ AppDataFunc&& app_data_func)
+{
+ if (GContext->IO.manualCallbacks)
+ {
+ GContext->callbackRegistry->jobs.push_back({owner, callback, user_data, sender, alias, std::forward(app_data_func)});
+ return;
+ }
+ mvSubmitCallback([=, app_data_func = std::forward(app_data_func)] () {
+ auto liveOwner = owner.lock(); // we need it to live through the mvRunCallback, hence constructing it separately rather than within "if"
+ if (liveOwner)
+ mvRunCallback(callback, *user_data, sender, alias, mvPyObject(app_data_func()));
+ });
+}
+
+// This overload exists purely to provide default argument values - we can't do this
+// directly on the template version above because the compiler won't be able to deduce
+// `app_data_func` type if `app_data_func` is omitted (that is, we can't really use
+// the default on `app_data_func`).
+void mvAddCallback(const std::weak_ptr& owner,
+ PyObject* callback,
+ const std::shared_ptr& user_data,
+ mvUUID sender = 0,
+ const std::string& alias = "");
+
+template
+void mvAddOwnerlessCallback(const std::shared_ptr& callback,
+ const std::shared_ptr& user_data,
+ mvUUID sender,
+ const std::string& alias,
+ AppDataFunc&& app_data_func)
+{
+ if (GContext->IO.manualCallbacks)
+ {
+ GContext->callbackRegistry->jobs.push_back({{}, nullptr, user_data, sender, alias, std::forward(app_data_func), callback});
+ return;
+ }
+ mvSubmitCallback([=, app_data_func = std::forward(app_data_func)]() {
+ mvRunCallback(*callback, *user_data, sender, alias, mvPyObject(app_data_func()));
+ });
+}
+
+// This overload exists purely to provide default argument values - we can't do this
+// directly on the template version above because the compiler won't be able to deduce
+// `app_data_func` type if `app_data_func` is omitted (that is, we can't really use
+// the default on `app_data_func`).
+void mvAddOwnerlessCallback(const std::shared_ptr& callback,
+ const std::shared_ptr& user_data,
+ mvUUID sender = 0,
+ const std::string& alias = "");
+
bool mvRunCallbacks();
template
@@ -247,7 +313,7 @@ std::future::type> mvSubmitTask(F f)
std::packaged_task task(std::move(f));
std::future res(task.get_future());
- if (GContext->started)
+ if (GContext->running)
GContext->callbackRegistry->tasks.push(std::move(task));
else
task();
@@ -256,11 +322,12 @@ std::future::type> mvSubmitTask(F f)
}
template
-std::future::type> mvSubmitCallback(F f)
+std::future::type> mvSubmitCallback(F f, bool ignore_limit = false)
{
- if (GContext->callbackRegistry->callCount > GContext->callbackRegistry->maxNumberOfCalls)
+ if (GContext->callbackRegistry->callCount > GContext->callbackRegistry->maxNumberOfCalls && !ignore_limit)
{
+ assert(false);
return {};
}
diff --git a/src/mvColors.cpp b/src/mvColors.cpp
index 2916cdacd..beffd8572 100644
--- a/src/mvColors.cpp
+++ b/src/mvColors.cpp
@@ -66,10 +66,7 @@ DearPyGui::draw_color_button(ImDrawList* drawlist, mvAppItem& item, mvColorButto
if (ImGui::ColorButton(item.info.internalLabel.c_str(), col, config.flags, ImVec2((float)item.config.width, (float)item.config.height)))
{
- if(item.config.alias.empty())
- mvAddCallback(item.getCallback(false), item.uuid, nullptr, item.config.user_data);
- else
- mvAddCallback(item.getCallback(false), item.config.alias, nullptr, item.config.user_data);
+ item.submitCallback();
}
}
@@ -161,11 +158,7 @@ DearPyGui::draw_color_edit(ImDrawList* drawlist, mvAppItem& item, mvColorEditCon
if (ImGui::ColorEdit4(item.info.internalLabel.c_str(), item.config.enabled ? config.value->data() : &config.disabled_value[0], config.flags))
{
mvColor color = mvColor((*config.value)[0], (*config.value)[1], (*config.value)[2], (*config.value)[3]);
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, color]() { mvAddCallback(item.getCallback(false), item.uuid, ToPyColor(color), item.config.user_data); });
- else
- mvSubmitCallback([&item, color]() { mvAddCallback(item.getCallback(false), item.config.alias, ToPyColor(color), item.config.user_data); });
+ item.submitCallback(color);
}
}
@@ -261,10 +254,7 @@ DearPyGui::draw_color_map_button(ImDrawList* drawlist, mvAppItem& item, mvColorM
ScopedID id(item.uuid);
if (ImPlot::ColormapButton(item.info.internalLabel.c_str(), ImVec2((float)item.config.width, (float)item.config.height), config.colorMap))
{
- if (item.config.alias.empty())
- mvAddCallback(item.getCallback(false), item.uuid, nullptr, item.config.user_data);
- else
- mvAddCallback(item.getCallback(false), item.config.alias, nullptr, item.config.user_data);
+ item.submitCallback();
}
}
@@ -442,11 +432,7 @@ DearPyGui::draw_color_picker(ImDrawList* drawlist, mvAppItem& item, mvColorPicke
if (ImGui::ColorPicker4(item.info.internalLabel.c_str(), item.config.enabled ? config.value->data() : &config.disabled_value[0], config.flags))
{
mvColor color = mvColor((*config.value)[0], (*config.value)[1], (*config.value)[2], (*config.value)[3]);
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, color]() { mvAddCallback(item.getCallback(false), item.uuid, ToPyColor(color), item.config.user_data); });
- else
- mvSubmitCallback([&item, color]() { mvAddCallback(item.getCallback(false), item.config.alias, ToPyColor(color), item.config.user_data); });
+ item.submitCallback(color);
}
}
@@ -535,12 +521,7 @@ DearPyGui::draw_color_map_slider(ImDrawList* drawlist, mvAppItem& item, mvColorM
if (ImPlot::ColormapSlider(item.info.internalLabel.c_str(), config.value.get(), &config.color, "", config.colorMap))
{
- auto value = *config.value;
-
- if (item.config.alias.empty())
- mvSubmitCallback([&item, value]() { mvAddCallback(item.getCallback(false), item.uuid, ToPyFloat(value), item.config.user_data); });
- else
- mvSubmitCallback([&item, value]() { mvAddCallback(item.getCallback(false), item.config.alias, ToPyFloat(value), item.config.user_data); });
+ item.submitCallback(*config.value);
}
}
diff --git a/src/mvContainers.cpp b/src/mvContainers.cpp
index 045dc662d..93969a59a 100644
--- a/src/mvContainers.cpp
+++ b/src/mvContainers.cpp
@@ -98,21 +98,11 @@ DearPyGui::fill_configuration_dict(const mvDragPayloadConfig& inConfig, PyObject
if (outDict == nullptr)
return;
- if (inConfig.dragData)
- {
- Py_XINCREF(inConfig.dragData);
- PyDict_SetItemString(outDict, "drag_data", inConfig.dragData);
- }
- else
- PyDict_SetItemString(outDict, "drag_data", GetPyNone());
+ PyObject* dragData = *(inConfig.dragData);
+ PyDict_SetItemString(outDict, "drag_data", dragData? dragData : Py_None);
- if (inConfig.dropData)
- {
- Py_XINCREF(inConfig.dropData);
- PyDict_SetItemString(outDict, "drop_data", inConfig.dropData);
- }
- else
- PyDict_SetItemString(outDict, "drop_data", GetPyNone());
+ PyObject* dropData = *(inConfig.dropData);
+ PyDict_SetItemString(outDict, "drop_data", dropData? dropData : Py_None);
}
void
@@ -190,13 +180,9 @@ DearPyGui::fill_configuration_dict(const mvWindowAppItemConfig& inConfig, PyObje
PyDict_SetItemString(outDict, "collapsed", mvPyObject(ToPyBool(inConfig.collapsed)));
PyDict_SetItemString(outDict, "min_size", mvPyObject(ToPyPairII(inConfig.min_size.x, inConfig.min_size.y)));
PyDict_SetItemString(outDict, "max_size", mvPyObject(ToPyPairII(inConfig.max_size.x, inConfig.max_size.y)));
- if (inConfig.on_close)
- {
- Py_XINCREF(inConfig.on_close);
- PyDict_SetItemString(outDict, "on_close", inConfig.on_close);
- }
- else
- PyDict_SetItemString(outDict, "on_close", GetPyNone());
+
+ PyObject* on_close = inConfig.on_close;
+ PyDict_SetItemString(outDict, "on_close", on_close? on_close : Py_None);
// helper to check and set bit
auto checkbitset = [outDict](const char* keyword, int flag, const int& flags)
@@ -322,20 +308,12 @@ DearPyGui::set_configuration(PyObject* inDict, mvDragPayloadConfig& outConfig)
if (PyObject* item = PyDict_GetItemString(inDict, "drag_data"))
{
- if (outConfig.dragData)
- Py_XDECREF(outConfig.dragData);
-
- Py_XINCREF(item);
- outConfig.dragData = item;
+ *outConfig.dragData = mvPyObject(item == Py_None? nullptr : item, true);
}
if (PyObject* item = PyDict_GetItemString(inDict, "drop_data"))
{
- if (outConfig.dropData)
- Py_XDECREF(outConfig.dropData);
-
- Py_XINCREF(item);
- outConfig.dropData = item;
+ *outConfig.dropData = mvPyObject(item == Py_None? nullptr : item, true);
}
}
@@ -449,12 +427,7 @@ DearPyGui::set_configuration(PyObject* inDict, mvAppItem& itemc, mvWindowAppItem
if (PyObject* item = PyDict_GetItemString(inDict, "on_close"))
{
- if (outConfig.on_close)
- Py_XDECREF(outConfig.on_close);
- item = SanitizeCallback(item);
- if (item)
- Py_XINCREF(item);
- outConfig.on_close = item;
+ outConfig.on_close = mvPyObject(item == Py_None? nullptr : item, true);
}
// helper for bit flipping
@@ -840,12 +813,7 @@ DearPyGui::draw_tab(ImDrawList* drawlist, mvAppItem& item, mvTabConfig& config)
// run call back if it exists
if (parent->getSpecificValue() != item.uuid)
{
- mvSubmitCallback([=, &item]() {
- if (parent->config.alias.empty())
- mvAddCallback(parent->getCallback(), parent->uuid, ToPyUUID(item.uuid), parent->config.user_data);
- else
- mvAddCallback(parent->getCallback(), parent->config.alias, ToPyUUID(item.uuid), parent->config.user_data);
- });
+ parent->submitCallback(item.uuid);
}
parent->setValue(item.uuid);
@@ -1163,14 +1131,31 @@ DearPyGui::draw_drag_payload(ImDrawList* drawlist, mvAppItem& item, mvDragPayloa
{
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID))
{
- ImGui::SetDragDropPayload(config.payloadType.c_str(), &item, sizeof(mvDragPayload));
-
- if (item.info.parentPtr->config.dragCallback)
+ // ImGui uses memcpy to store the drag data, and the only thing we can
+ // store this way more ore less safely is the numeric UUID. Worst case if
+ // the payload gets deleted during drag'n'drop, we'll simply abandon
+ // the drop callback.
+ ImGui::SetDragDropPayload(config.payloadType.c_str(), &item.uuid, sizeof(item.uuid));
+
+ // The mvDragPayload item is a bit peculiar: historically, it uses its own user_data
+ // to pass to the parent item's drag callback. That's why we can't directly
+ // call parentPtr->submitCallback(), as it would take user_data from the parent
+ // instead of mvDragPayload.
+ mvAppItem* parent = item.info.parentPtr;
+ if (parent->config.dragCallback)
{
- if (item.info.parentPtr->config.alias.empty())
- mvAddCallback(item.info.parentPtr->config.dragCallback, item.config.parent, config.dragData, item.config.user_data);
- else
- mvAddCallback(item.info.parentPtr->config.dragCallback, item.info.parentPtr->config.alias, config.dragData, item.config.user_data);
+ // We can't use mvAppItem::submitCallbackEx here because we need custom user_data.
+ mvAddCallback(
+ parent->weak_from_this(),
+ parent->config.dragCallback,
+ item.config.user_data,
+ parent->uuid, parent->config.alias,
+ [dragData=config.dragData] () {
+ PyObject* pyDragData = *dragData;
+ Py_XINCREF(pyDragData);
+ return pyDragData;
+ }
+ );
}
for (auto& childset : item.childslots)
@@ -1531,10 +1516,7 @@ DearPyGui::draw_window(ImDrawList* drawlist, mvAppItem& item, mvWindowAppItemCon
item.state.toggledOpen = false;
item.state.visible = false;
- if (item.config.alias.empty())
- mvAddCallback(config.on_close, item.uuid, nullptr, item.config.user_data);
- else
- mvAddCallback(config.on_close, item.config.alias, nullptr, item.config.user_data);
+ item.submitCallbackEx(config.on_close, []() -> PyObject* { return nullptr; });
// handle popping themes
cleanup_local_theming(&item);
@@ -1558,6 +1540,23 @@ DearPyGui::draw_window(ImDrawList* drawlist, mvAppItem& item, mvWindowAppItemCon
// handle popping themes
cleanup_local_theming(&item);
+
+ // See if it's just been closed (can't rely on BeginPopup == false here
+ // because BeginPopup can, even if only theoretically, return false for
+ // an open popup - e.g. when it has zero size).
+ if (!ImGui::IsPopupOpen(item.info.internalLabel.c_str()))
+ {
+ // Hide it so that the callback doesn't fire at the next frame
+ item.config.show = false;
+ // Update item state so that get_item_state is valid
+ item.state.lastFrameUpdate = GContext->frame;
+ item.state.hovered = false;
+ item.state.focused = false;
+ item.state.toggledOpen = false;
+ item.state.visible = false;
+ // Fire the close callback, if any
+ item.submitCallbackEx(config.on_close, []() -> PyObject* { return nullptr; });
+ }
return;
}
}
@@ -1711,16 +1710,45 @@ DearPyGui::draw_window(ImDrawList* drawlist, mvAppItem& item, mvWindowAppItemCon
item.state.toggledOpen = false;
item.state.visible = false;
- if (item.config.alias.empty())
- mvAddCallback(config.on_close, item.uuid, nullptr, item.config.user_data);
- else
- mvAddCallback(config.on_close, item.config.alias, nullptr, item.config.user_data);
+ item.submitCallbackEx(config.on_close, []() -> PyObject* { return nullptr; });
}
if (item.handlerRegistry)
item.handlerRegistry->checkEvents(&item.state);
}
+void
+check_drop_event(mvAppItem* item)
+{
+ if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(item->config.payloadType.c_str()))
+ {
+ IM_ASSERT(payload->DataSize == sizeof(mvUUID) && "Unexpected drag payload data size.");
+ mvUUID payloadUUID = *(mvUUID*)payload->Data;
+ // Let's see if the payload still exists.
+ auto payloadItem = GetItem(*GContext->itemRegistry, payloadUUID);
+ if (payloadItem && payloadItem->type == mvAppItemType::mvDragPayload)
+ {
+ auto payloadActual = static_cast(payloadItem);
+ // Note: we're passing None in user_data for backward compatibility
+ // (mvAddCallback will derive None from the mvPyObject that stores nullptr).
+ // One day this may change, but we need to decide which of the user_data's
+ // (parent's or payload's) we're going to pass here.
+ // We can't use mvAppItem::submitCallbackEx here because we need custom user_data.
+ mvAddCallback(
+ item->weak_from_this(),
+ item->config.dropCallback,
+ std::make_shared(nullptr),
+ item->uuid, item->config.alias,
+ [dragData = payloadActual->configData.dragData] () {
+ PyObject* pyDragData = *dragData;
+ Py_XINCREF(pyDragData);
+ return pyDragData;
+ }
+ );
+ }
+ }
+}
+
void
apply_drag_drop(mvAppItem* item)
{
@@ -1732,15 +1760,7 @@ apply_drag_drop(mvAppItem* item)
ScopedID id(item->uuid);
if (ImGui::BeginDragDropTarget())
{
- if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(item->config.payloadType.c_str()))
- {
- auto payloadActual = static_cast(payload->Data);
- if (item->config.alias.empty())
- mvAddCallback(item->config.dropCallback, item->uuid, payloadActual->configData.dragData, nullptr);
- else
- mvAddCallback(item->config.dropCallback, item->config.alias, payloadActual->configData.dragData, nullptr);
- }
-
+ check_drop_event(item);
ImGui::EndDragDropTarget();
}
}
@@ -1754,15 +1774,7 @@ apply_drag_drop_nodraw(mvAppItem* item)
ScopedID id(item->uuid);
if (ImGui::BeginDragDropTarget())
{
- if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(item->config.payloadType.c_str()))
- {
- auto payloadActual = static_cast(payload->Data);
- if (item->config.alias.empty())
- mvAddCallback(item->config.dropCallback, item->uuid, payloadActual->configData.dragData, nullptr);
- else
- mvAddCallback(item->config.dropCallback, item->config.alias, payloadActual->configData.dragData, nullptr);
- }
-
+ check_drop_event(item);
ImGui::EndDragDropTarget();
}
}
diff --git a/src/mvContainers.h b/src/mvContainers.h
index bc205f918..56ed8612c 100644
--- a/src/mvContainers.h
+++ b/src/mvContainers.h
@@ -3,6 +3,15 @@
#include "mvItemRegistry.h"
#include
+// check_drop_event() implements the typical contents of Dear ImGui's drop target
+// (ImGui::BeginDragDropTarget()) tied to DearPyGui's drop callback. You usually
+// don't need to call it directly, but it can be used need to implement custom
+// drag'n'drop mechanics.
+// To implement drag'n'drop in an arbitrary ImGui item, use `apply_drag_drop()` -
+// it both implements the entire drop target and renders the drag payload.
+void check_drop_event(mvAppItem* item);
+// During drag'n'drop, renders drag payload and checks whether it's time to call
+// the drop callback.
void apply_drag_drop(mvAppItem* item);
void apply_drag_drop_nodraw(mvAppItem* item);
@@ -118,8 +127,8 @@ struct mvGroupConfig
struct mvDragPayloadConfig
{
std::string payloadType = "$$DPG_PAYLOAD";
- PyObject* dragData = nullptr;
- PyObject* dropData = nullptr;
+ std::shared_ptr dragData = std::make_shared(nullptr);
+ std::shared_ptr dropData = std::make_shared(nullptr);
};
struct mvCollapsingHeaderConfig
@@ -161,7 +170,7 @@ struct mvWindowAppItemConfig
bool no_background = false;
bool collapsed = false;
bool no_open_over_existing_popup = true;
- PyObject* on_close = nullptr;
+ mvPyObject on_close = nullptr;
mvVec2 min_size = { 100.0f, 100.0f };
mvVec2 max_size = { 30000.0f, 30000.0f };
float scrollX = 0.0f;
@@ -288,5 +297,4 @@ class mvWindowAppItem : public mvAppItem
void draw(ImDrawList* drawlist, float x, float y) override { DearPyGui::draw_window(drawlist, *this, configData); }
void handleSpecificKeywordArgs(PyObject* dict) override { DearPyGui::set_configuration(dict, *this, configData); }
void getSpecificConfiguration(PyObject* dict) override { DearPyGui::fill_configuration_dict(configData, dict); }
- ~mvWindowAppItem() { PyObject* callback = configData.on_close; mvSubmitCallback([callback]() { if (callback) Py_XDECREF(callback);});}
};
\ No newline at end of file
diff --git a/src/mvContext.cpp b/src/mvContext.cpp
index 00200ca9f..586714880 100644
--- a/src/mvContext.cpp
+++ b/src/mvContext.cpp
@@ -202,6 +202,11 @@ GetParsers()
return const_cast&>(GetModuleParsers());
}
+void StopRendering()
+{
+ GContext->running = false;
+}
+
void
InsertConstants_mvContext(std::vector>& constants)
{
diff --git a/src/mvContext.h b/src/mvContext.h
index 10ac7106c..bed6fd2da 100644
--- a/src/mvContext.h
+++ b/src/mvContext.h
@@ -34,6 +34,8 @@ mvUUID GenerateUUID();
void SetDefaultTheme();
void Render();
std::map& GetParsers();
+// Signals the rendering loop via GContext->running to quit.
+void StopRendering();
struct mvInput
{
@@ -101,7 +103,11 @@ struct mvIO
struct mvContext
{
std::atomic_bool waitOneFrame = false;
+ // Indicates whether DPG has started at least once in this context, i.e. whether
+ // associated Dear ImGui contexts exist and can be read from.
std::atomic_bool started = false;
+ // If true, more frames are going to be rendered. Goes back to false on shutdown.
+ std::atomic_bool running = false;
std::recursive_mutex mutex;
std::future future;
float deltaTime = 0.0f; // time since last frame
diff --git a/src/mvDatePicker.cpp b/src/mvDatePicker.cpp
index ef309c623..82dab312a 100644
--- a/src/mvDatePicker.cpp
+++ b/src/mvDatePicker.cpp
@@ -67,13 +67,7 @@ void mvDatePicker::draw(ImDrawList* drawlist, float x, float y)
{
ImPlot::GetGmtTime(*_imvalue, _value.get());
{
- auto value = *_value;
- mvSubmitCallback([=]() {
- if(config.alias.empty())
- mvAddCallback(getCallback(false), uuid, ToPyTime(value), config.user_data);
- else
- mvAddCallback(getCallback(false), config.alias, ToPyTime(value), config.user_data);
- });
+ submitCallback(*_value);
}
}
}
diff --git a/src/mvDrawings.cpp b/src/mvDrawings.cpp
index c24cc61a6..bb5d082d1 100644
--- a/src/mvDrawings.cpp
+++ b/src/mvDrawings.cpp
@@ -952,10 +952,7 @@ void mvDrawlist::draw(ImDrawList* drawlist, float x, float y)
if (ImGui::InvisibleButton(info.internalLabel.c_str(), ImVec2((float)config.width, (float)config.height), ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight | ImGuiButtonFlags_MouseButtonMiddle))
{
- if (config.alias.empty())
- mvAddCallback(getCallback(false), uuid, nullptr, config.user_data);
- else
- mvAddCallback(getCallback(false), config.alias, nullptr, config.user_data);
+ submitCallback();
}
UpdateAppItemState(state);
diff --git a/src/mvFileDialog.cpp b/src/mvFileDialog.cpp
index 7eef5a3c1..5f9d7fb8e 100644
--- a/src/mvFileDialog.cpp
+++ b/src/mvFileDialog.cpp
@@ -103,25 +103,21 @@ void mvFileDialog::draw(ImDrawList* drawlist, float x, float y)
// action if OK clicked or if cancel clicked and cancel callback provided
if (_instance.IsOk() || _cancelCallback)
{
- mvSubmitCallback([&]()
+ submitCallbackEx(
+ _instance.IsOk()? config.callback : _cancelCallback,
+ // We're capturing a weak reference to mvFileDialog, so that the dialog can
+ // be safely deleted even if there's a callback still waiting in the queue.
+ [weakThis=weak_from_this()] () -> PyObject*
{
- PyObject* callback;
- PyObject* appData;
-
- if(_instance.IsOk())
+ auto liveThis = weakThis.lock();
+ if (liveThis)
{
- callback = config.callback;
- appData = getInfoDict();
- } else {
- callback = _cancelCallback;
- appData = getInfoDict();
+ mvFileDialog* dlg = static_cast(liveThis.get());
+ return dlg->getInfoDict();
}
-
- if(config.alias.empty())
- mvRunCallback(callback, uuid, appData, config.user_data);
- else
- mvRunCallback(callback, config.alias, appData, config.user_data);
- });
+ return nullptr;
+ }
+ );
}
// close
@@ -189,15 +185,7 @@ void mvFileDialog::handleSpecificKeywordArgs(PyObject* dict)
if (PyObject* item = PyDict_GetItemString(dict, "cancel_callback"))
{
- Py_XDECREF(_cancelCallback);
-
- if (item == Py_None)
- _cancelCallback = nullptr;
- else
- {
- Py_XINCREF(item);
- _cancelCallback = item;
- }
+ _cancelCallback = mvPyObject(item == Py_None? nullptr : item, true);
}
}
diff --git a/src/mvFileDialog.h b/src/mvFileDialog.h
index 3b034a4a3..547c22021 100644
--- a/src/mvFileDialog.h
+++ b/src/mvFileDialog.h
@@ -46,5 +46,5 @@ class mvFileDialog final : public mvAppItem
bool _directory = false;
mvVec2 _min_size = { 100.0f, 100.0f };
mvVec2 _max_size = { 30000.0f, 30000.0f };
- PyObject* _cancelCallback = nullptr;
+ mvPyObject _cancelCallback = nullptr;
};
\ No newline at end of file
diff --git a/src/mvGlobalHandlers.cpp b/src/mvGlobalHandlers.cpp
index 791e1126e..5382f37c0 100644
--- a/src/mvGlobalHandlers.cpp
+++ b/src/mvGlobalHandlers.cpp
@@ -19,26 +19,14 @@ void mvKeyDownHandler::draw(ImDrawList* drawlist, float x, float y)
auto key = ImGui::GetKeyData(static_cast(i));
if (key->Down)
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyMPair(i, key->DownDuration), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyMPair(i, key->DownDuration), config.user_data);
- });
+ submitCallbackEx([=]() { return ToPyMPair(i, key->DownDuration); });
}
}
}
else if (ImGui::IsKeyDown(_key))
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyMPair(_key, ImGui::GetKeyData(_key)->DownDuration), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyMPair(_key, ImGui::GetKeyData(_key)->DownDuration), config.user_data);
- });
+ submitCallbackEx([=]() { return ToPyMPair(_key, ImGui::GetKeyData(_key)->DownDuration); });
}
}
@@ -74,26 +62,14 @@ void mvKeyPressHandler::draw(ImDrawList* drawlist, float x, float y)
{
if (ImGui::IsKeyPressed(static_cast(i)))
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyInt(i), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyInt(i), config.user_data);
- });
+ submitCallback(i);
}
}
}
else if (ImGui::IsKeyPressed(_key))
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyInt(_key), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyInt(_key), config.user_data);
- });
+ submitCallback(_key);
}
}
@@ -141,26 +117,14 @@ void mvKeyReleaseHandler::draw(ImDrawList* drawlist, float x, float y)
{
if (ImGui::IsKeyReleased(static_cast(i)))
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyInt(i), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyInt(i), config.user_data);
- });
+ submitCallback(i);
}
}
}
else if (ImGui::IsKeyReleased(_key))
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyInt(_key), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyInt(_key), config.user_data);
- });
+ submitCallback(_key);
}
}
@@ -208,26 +172,14 @@ void mvMouseClickHandler::draw(ImDrawList* drawlist, float x, float y)
{
if (ImGui::IsMouseClicked(i))
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyInt(i), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyInt(i), config.user_data);
- });
+ submitCallback(i);
}
}
}
else if (ImGui::IsMouseClicked(_button))
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyInt(_button), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyInt(_button), config.user_data);
- });
+ submitCallback(_button);
}
}
@@ -275,26 +227,14 @@ void mvMouseDoubleClickHandler::draw(ImDrawList* drawlist, float x, float y)
{
if (ImGui::IsMouseDoubleClicked(i))
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyInt(i), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyInt(i), config.user_data);
- });
+ submitCallback(i);
}
}
}
else if (ImGui::IsMouseDoubleClicked(_button))
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyInt(_button), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyInt(_button), config.user_data);
- });
+ submitCallback(_button);
}
}
@@ -342,26 +282,14 @@ void mvMouseDownHandler::draw(ImDrawList* drawlist, float x, float y)
{
if (ImGui::GetIO().MouseDown[i])
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyMPair(i, ImGui::GetIO().MouseDownDuration[i]), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyMPair(i, ImGui::GetIO().MouseDownDuration[i]), config.user_data);
- });
+ submitCallbackEx([=]() { return ToPyMPair(i, ImGui::GetIO().MouseDownDuration[i]); });
}
}
}
else if (ImGui::GetIO().MouseDown[_button])
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyMPair(_button, ImGui::GetIO().MouseDownDuration[_button]), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyMPair(_button, ImGui::GetIO().MouseDownDuration[_button]), config.user_data);
- });
+ submitCallbackEx([=]() { return ToPyMPair(_button, ImGui::GetIO().MouseDownDuration[_button]); });
}
}
@@ -412,15 +340,7 @@ void mvMouseDragHandler::draw(ImDrawList* drawlist, float x, float y)
if (ImGui::IsMouseDragging(i, _threshold))
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid,
- ToPyMTrip(i, ImGui::GetMouseDragDelta(i).x, ImGui::GetMouseDragDelta(i).y), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias,
- ToPyMTrip(i, ImGui::GetMouseDragDelta(i).x, ImGui::GetMouseDragDelta(i).y), config.user_data);
- });
+ submitCallbackEx([=]() { return ToPyMTrip(i, ImGui::GetMouseDragDelta(i).x, ImGui::GetMouseDragDelta(i).y); });
}
}
}
@@ -429,15 +349,8 @@ void mvMouseDragHandler::draw(ImDrawList* drawlist, float x, float y)
{
if (ImGui::IsMouseReleased(_button))
ImGui::ResetMouseDragDelta(_button);
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid,
- ToPyMTrip(_button, ImGui::GetMouseDragDelta(_button).x, ImGui::GetMouseDragDelta(_button).y), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias,
- ToPyMTrip(_button, ImGui::GetMouseDragDelta(_button).x, ImGui::GetMouseDragDelta(_button).y), config.user_data);
- });
+
+ submitCallbackEx([=]() { return ToPyMTrip(_button, ImGui::GetMouseDragDelta(_button).x, ImGui::GetMouseDragDelta(_button).y); });
}
}
@@ -495,13 +408,7 @@ void mvMouseMoveHandler::draw(ImDrawList* drawlist, float x, float y)
{
_oldPos = mousepos;
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyPair(mousepos.x, mousepos.y), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyPair(mousepos.x, mousepos.y), config.user_data);
- });
+ submitCallback(mousepos);
}
}
}
@@ -514,26 +421,14 @@ void mvMouseReleaseHandler::draw(ImDrawList* drawlist, float x, float y)
{
if (ImGui::IsMouseReleased(i))
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyInt(i), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyInt(i), config.user_data);
- });
+ submitCallback(i);
}
}
}
else if (ImGui::IsMouseReleased(_button))
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyInt(_button), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyInt(_button), config.user_data);
- });
+ submitCallback(_button);
}
}
@@ -578,14 +473,6 @@ void mvMouseWheelHandler::draw(ImDrawList* drawlist, float x, float y)
int wheel = (int)ImGui::GetIO().MouseWheel;
if (wheel)
{
-
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyInt(wheel), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyInt(wheel), config.user_data);
- });
-
+ submitCallback(wheel);
}
}
\ No newline at end of file
diff --git a/src/mvItemHandlers.cpp b/src/mvItemHandlers.cpp
index 93e79a2ad..01dca1844 100644
--- a/src/mvItemHandlers.cpp
+++ b/src/mvItemHandlers.cpp
@@ -124,19 +124,20 @@ void mvItemHandlerRegistry::onBind(mvAppItem* item)
}
}
+void mvItemHandler::submitHandler(mvAppItem* parent)
+{
+ submitCallbackEx([uuid=parent->uuid, alias=parent->config.alias] () {
+ return ToPyUUID(uuid, alias);
+ });
+}
+
void mvActivatedHandler::customAction(void* data)
{
mvAppItemState* state = static_cast(data);
if (state->activated)
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyUUID(state->parent), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyUUID(state->parent), config.user_data);
- });
+ submitHandler(state->parent);
}
}
@@ -146,13 +147,7 @@ void mvActiveHandler::customAction(void* data)
mvAppItemState* state = static_cast(data);
if (state->active)
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyUUID(state->parent), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyUUID(state->parent), config.user_data);
- });
+ submitHandler(state->parent);
}
}
@@ -160,51 +155,25 @@ void mvClickedHandler::customAction(void* data)
{
mvAppItemState* state = static_cast(data);
- if (_button == -1 || _button == 0)
- if (state->leftclicked)
- {
- mvSubmitCallback([=]()
- {
- mvPyObject pArgs(PyTuple_New(2));
- PyTuple_SetItem(pArgs, 0, ToPyInt(0));
- PyTuple_SetItem(pArgs, 1, ToPyUUID(state->parent)); // steals data, so don't deref
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, pArgs, config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, pArgs, config.user_data);
- });
- }
- if (_button == -1 || _button == 1)
- if (state->rightclicked)
- {
- mvSubmitCallback([=]()
- {
- mvPyObject pArgs(PyTuple_New(2));
- PyTuple_SetItem(pArgs, 0, ToPyInt(1));
- PyTuple_SetItem(pArgs, 1, ToPyUUID(state->parent)); // steals data, so don't deref
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, pArgs, config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, pArgs, config.user_data);
- });
- }
+ b8 clicked[] = {state->leftclicked, state->rightclicked, state->middleclicked};
+
+ int i = (_button < 0)? 0 : _button ;
+ int end = (_button < 0)? (int)std::size(clicked) : (i + 1);
- if (_button == -1 || _button == 2)
- if (state->middleclicked)
+ for (; i < end; i++)
+ {
+ if (clicked[i])
{
- mvSubmitCallback([=]()
- {
- mvPyObject pArgs(PyTuple_New(2));
- PyTuple_SetItem(pArgs, 0, ToPyInt(2));
- PyTuple_SetItem(pArgs, 1, ToPyUUID(state->parent)); // steals data, so don't deref
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, pArgs, config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, pArgs, config.user_data);
- });
+ mvAppItem* parent = state->parent;
+ submitCallbackEx([i, uuid=parent->uuid, alias=parent->config.alias] () {
+ PyObject* app_data = PyTuple_New(2);
+ PyTuple_SetItem(app_data, 0, ToPyInt(i));
+ PyTuple_SetItem(app_data, 1, ToPyUUID(uuid, alias));
+ return app_data;
+ });
}
-
+ }
}
void mvClickedHandler::handleSpecificRequiredArgs(PyObject* dict)
@@ -242,16 +211,13 @@ void mvDoubleClickedHandler::customAction(void* data)
{
if (state->doubleclicked[i])
{
- mvSubmitCallback([=]()
- {
- mvPyObject pArgs(PyTuple_New(2));
- PyTuple_SetItem(pArgs, 0, ToPyInt(i));
- PyTuple_SetItem(pArgs, 1, ToPyUUID(state->parent)); // steals data, so don't deref
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, pArgs, config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, pArgs, config.user_data);
- });
+ mvAppItem* parent = state->parent;
+ submitCallbackEx([i, uuid=parent->uuid, alias=parent->config.alias] () {
+ PyObject* app_data = PyTuple_New(2);
+ PyTuple_SetItem(app_data, 0, ToPyInt(i));
+ PyTuple_SetItem(app_data, 1, ToPyUUID(uuid, alias));
+ return app_data;
+ });
}
}
}
@@ -286,13 +252,7 @@ void mvDeactivatedAfterEditHandler::customAction(void* data)
mvAppItemState* state = static_cast(data);
if (state->deactivatedAfterEdit)
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyUUID(state->parent), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyUUID(state->parent), config.user_data);
- });
+ submitHandler(state->parent);
}
}
@@ -301,13 +261,7 @@ void mvDeactivatedHandler::customAction(void* data)
mvAppItemState* state = static_cast(data);
if (state->deactivated)
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyUUID(state->parent), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyUUID(state->parent), config.user_data);
- });
+ submitHandler(state->parent);
}
}
@@ -317,13 +271,7 @@ void mvEditedHandler::customAction(void* data)
mvAppItemState* state = static_cast(data);
if (state->edited)
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyUUID(state->parent), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyUUID(state->parent), config.user_data);
- });
+ submitHandler(state->parent);
}
}
@@ -333,13 +281,7 @@ void mvFocusHandler::customAction(void* data)
mvAppItemState* state = static_cast(data);
if (state->focused)
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyUUID(state->parent), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyUUID(state->parent), config.user_data);
- });
+ submitHandler(state->parent);
}
}
@@ -348,13 +290,7 @@ void mvHoverHandler::customAction(void* data)
mvAppItemState* state = static_cast(data);
if (state->hovered)
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyUUID(state->parent), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyUUID(state->parent), config.user_data);
- });
+ submitHandler(state->parent);
}
}
@@ -363,42 +299,24 @@ void mvResizeHandler::customAction(void* data)
mvAppItemState* state = static_cast(data);
if (state->mvRectSizeResized)
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, ToPyUUID(state->parent), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, ToPyUUID(state->parent), config.user_data);
- });
+ submitHandler(state->parent);
}
}
void mvToggledOpenHandler::customAction(void* data)
{
-
- if (static_cast(data)->toggledOpen)
+ mvAppItemState* state = static_cast(data);
+ if (state->toggledOpen)
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, GetPyNone(), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, GetPyNone(), config.user_data);
- });
+ submitHandler(state->parent);
}
}
void mvVisibleHandler::customAction(void* data)
{
-
+ mvAppItemState* state = static_cast(data);
if (static_cast(data)->visible)
{
- mvSubmitCallback([=]()
- {
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, GetPyNone(), config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, GetPyNone(), config.user_data);
- });
+ submitHandler(state->parent);
}
-}
\ No newline at end of file
+}
diff --git a/src/mvItemHandlers.h b/src/mvItemHandlers.h
index be2823245..f49aa92eb 100644
--- a/src/mvItemHandlers.h
+++ b/src/mvItemHandlers.h
@@ -14,29 +14,38 @@ class mvItemHandlerRegistry : public mvAppItem
void onBind(mvAppItem* item);
};
-class mvActivatedHandler : public mvAppItem
+class mvItemHandler : public mvAppItem
{
public:
- explicit mvActivatedHandler(mvUUID uuid) : mvAppItem(uuid) {}
+ explicit mvItemHandler(mvUUID uuid) : mvAppItem(uuid) {}
+
+protected:
+ void submitHandler(mvAppItem* parent);
+};
+
+class mvActivatedHandler : public mvItemHandler
+{
+public:
+ explicit mvActivatedHandler(mvUUID uuid) : mvItemHandler(uuid) {}
void draw(ImDrawList* drawlist, float x, float y) override {}
void customAction(void* data = nullptr) override;
};
-class mvActiveHandler : public mvAppItem
+class mvActiveHandler : public mvItemHandler
{
public:
- explicit mvActiveHandler(mvUUID uuid) : mvAppItem(uuid) {}
+ explicit mvActiveHandler(mvUUID uuid) : mvItemHandler(uuid) {}
void draw(ImDrawList* drawlist, float x, float y) override {}
void customAction(void* data = nullptr) override;
};
-class mvClickedHandler : public mvAppItem
+class mvClickedHandler : public mvItemHandler
{
public:
int _button = -1;
- explicit mvClickedHandler(mvUUID uuid) : mvAppItem(uuid) {}
+ explicit mvClickedHandler(mvUUID uuid) : mvItemHandler(uuid) {}
void draw(ImDrawList* drawlist, float x, float y) override {}
void customAction(void* data = nullptr) override;
void handleSpecificRequiredArgs(PyObject* dict) override;
@@ -44,11 +53,11 @@ class mvClickedHandler : public mvAppItem
void getSpecificConfiguration(PyObject* dict) override;
};
-class mvDoubleClickedHandler : public mvAppItem
+class mvDoubleClickedHandler : public mvItemHandler
{
public:
int _button = -1;
- explicit mvDoubleClickedHandler(mvUUID uuid) : mvAppItem(uuid) {}
+ explicit mvDoubleClickedHandler(mvUUID uuid) : mvItemHandler(uuid) {}
void draw(ImDrawList* drawlist, float x, float y) override {}
void customAction(void* data = nullptr) override;
void handleSpecificRequiredArgs(PyObject* dict) override;
@@ -56,66 +65,66 @@ class mvDoubleClickedHandler : public mvAppItem
void getSpecificConfiguration(PyObject* dict) override;
};
-class mvDeactivatedAfterEditHandler : public mvAppItem
+class mvDeactivatedAfterEditHandler : public mvItemHandler
{
public:
- explicit mvDeactivatedAfterEditHandler(mvUUID uuid) : mvAppItem(uuid) {}
+ explicit mvDeactivatedAfterEditHandler(mvUUID uuid) : mvItemHandler(uuid) {}
void draw(ImDrawList* drawlist, float x, float y) override {}
void customAction(void* data = nullptr) override;
};
-class mvDeactivatedHandler : public mvAppItem
+class mvDeactivatedHandler : public mvItemHandler
{
public:
- explicit mvDeactivatedHandler(mvUUID uuid) : mvAppItem(uuid) {}
+ explicit mvDeactivatedHandler(mvUUID uuid) : mvItemHandler(uuid) {}
void draw(ImDrawList* drawlist, float x, float y) override {}
void customAction(void* data = nullptr) override;
};
-class mvEditedHandler : public mvAppItem
+class mvEditedHandler : public mvItemHandler
{
public:
- explicit mvEditedHandler(mvUUID uuid) : mvAppItem(uuid) {}
+ explicit mvEditedHandler(mvUUID uuid) : mvItemHandler(uuid) {}
void draw(ImDrawList* drawlist, float x, float y) override {}
void customAction(void* data = nullptr) override;
};
-class mvFocusHandler : public mvAppItem
+class mvFocusHandler : public mvItemHandler
{
public:
- explicit mvFocusHandler(mvUUID uuid) : mvAppItem(uuid) {}
+ explicit mvFocusHandler(mvUUID uuid) : mvItemHandler(uuid) {}
void draw(ImDrawList* drawlist, float x, float y) override {}
void customAction(void* data = nullptr) override;
};
-class mvHoverHandler : public mvAppItem
+class mvHoverHandler : public mvItemHandler
{
public:
- explicit mvHoverHandler(mvUUID uuid) : mvAppItem(uuid) {}
+ explicit mvHoverHandler(mvUUID uuid) : mvItemHandler(uuid) {}
void draw(ImDrawList* drawlist, float x, float y) override {}
void customAction(void* data = nullptr) override;
};
-class mvResizeHandler : public mvAppItem
+class mvResizeHandler : public mvItemHandler
{
public:
- explicit mvResizeHandler(mvUUID uuid) : mvAppItem(uuid) {}
+ explicit mvResizeHandler(mvUUID uuid) : mvItemHandler(uuid) {}
void draw(ImDrawList* drawlist, float x, float y) override {}
void customAction(void* data = nullptr) override;
};
-class mvToggledOpenHandler : public mvAppItem
+class mvToggledOpenHandler : public mvItemHandler
{
public:
- explicit mvToggledOpenHandler(mvUUID uuid) : mvAppItem(uuid) {}
+ explicit mvToggledOpenHandler(mvUUID uuid) : mvItemHandler(uuid) {}
void draw(ImDrawList* drawlist, float x, float y) override {}
void customAction(void* data = nullptr) override;
};
-class mvVisibleHandler : public mvAppItem
+class mvVisibleHandler : public mvItemHandler
{
public:
- explicit mvVisibleHandler(mvUUID uuid) : mvAppItem(uuid) {}
+ explicit mvVisibleHandler(mvUUID uuid) : mvItemHandler(uuid) {}
void draw(ImDrawList* drawlist, float x, float y) override {}
void customAction(void* data = nullptr) override;
-};
\ No newline at end of file
+};
diff --git a/src/mvItemRegistry.cpp b/src/mvItemRegistry.cpp
index 442c27423..07e403511 100644
--- a/src/mvItemRegistry.cpp
+++ b/src/mvItemRegistry.cpp
@@ -1075,7 +1075,7 @@ RenderItemRegistry(mvItemRegistry& registry)
DebugItem("Enabled:", root->config.enabled ? ts : fs);
DebugItem("Tracked:", root->config.tracked ? ts : fs);
DebugItem("Callback:", root->config.callback ? ts : fs);
- DebugItem("User Data:", root->config.user_data ? ts : fs);
+ DebugItem("User Data:", *(root->config.user_data) ? ts : fs);
DebugItem("Drop Callback:", root->config.dropCallback ? ts : fs);
DebugItem("Drag Callback:", root->config.dragCallback ? ts : fs);
@@ -1275,8 +1275,7 @@ AddItemWithRuntimeChecks(mvItemRegistry& registry, std::shared_ptr it
// this is a unique situation in that the caller always has the GIL
registry.capturedItem = item;
- mvRunCallback(registry.captureCallback, registry.capturedItem->uuid, nullptr, nullptr);
- Py_XDECREF(registry.captureCallback);
+ mvRunCallback(registry.captureCallback, nullptr, registry.capturedItem->uuid);
registry.captureCallback = nullptr;
return true;
}
diff --git a/src/mvItemRegistry.h b/src/mvItemRegistry.h
index e6a30b89c..1979b9748 100644
--- a/src/mvItemRegistry.h
+++ b/src/mvItemRegistry.h
@@ -83,8 +83,8 @@ struct mvItemRegistry
b8 showImPlotDebug = false;
std::vector> debugWindows;
std::shared_ptr capturedItem = nullptr;
- PyObject* captureCallback = nullptr;
- PyObject* captureCallbackUserData = nullptr;
+ mvPyObject captureCallback = nullptr;
+ mvPyObject captureCallbackUserData = nullptr;
// roots
std::vector> colormapRoots;
diff --git a/src/mvLayoutWindow.cpp b/src/mvLayoutWindow.cpp
index 54bb78eb4..d528542a5 100644
--- a/src/mvLayoutWindow.cpp
+++ b/src/mvLayoutWindow.cpp
@@ -210,7 +210,7 @@ void mvLayoutWindow::drawWidgets()
DebugItem("Enabled:", _itemref->config.enabled ? ts : fs);
DebugItem("Tracked:", _itemref->config.tracked ? ts : fs);
DebugItem("Callback:", _itemref->config.callback ? ts : fs);
- DebugItem("User Data:", _itemref->config.user_data ? ts : fs);
+ DebugItem("User Data:", *(_itemref->config.user_data) ? ts : fs);
DebugItem("Drop Callback:", _itemref->config.dropCallback ? ts : fs);
DebugItem("Drag Callback:", _itemref->config.dragCallback ? ts : fs);
diff --git a/src/mvNodes.cpp b/src/mvNodes.cpp
index d3d7d6124..71635ddfd 100644
--- a/src/mvNodes.cpp
+++ b/src/mvNodes.cpp
@@ -52,13 +52,7 @@ void mvNodeEditor::handleSpecificKeywordArgs(PyObject* dict)
if (PyObject* item = PyDict_GetItemString(dict, "delink_callback"))
{
-
- if (_delinkCallback)
- Py_XDECREF(_delinkCallback);
- item = SanitizeCallback(item);
- if (item)
- Py_XINCREF(item);
- _delinkCallback = item;
+ _delinkCallback = mvPyObject(item == Py_None? nullptr : item, true);
}
// helper for bit flipping
@@ -79,13 +73,7 @@ void mvNodeEditor::getSpecificConfiguration(PyObject* dict)
if (dict == nullptr)
return;
- if (_delinkCallback)
- {
- Py_XINCREF(_delinkCallback);
- PyDict_SetItemString(dict, "delink_callback", _delinkCallback);
- }
- else
- PyDict_SetItemString(dict, "delink_callback", GetPyNone());
+ PyDict_SetItemString(dict, "delink_callback", _delinkCallback? (PyObject*)_delinkCallback : Py_None);
// helper to check and set bit
auto checkbitset = [dict](const char* keyword, int flag, const int& flags)
@@ -333,20 +321,12 @@ void mvNodeEditor::draw(ImDrawList* drawlist, float x, float y)
if (config.callback)
{
- if (config.alias.empty())
- mvSubmitCallback([=]() {
- PyObject* link = PyTuple_New(2);
- PyTuple_SetItem(link, 0, ToPyUUID(node1));
- PyTuple_SetItem(link, 1, ToPyUUID(node2));
- mvAddCallback(config.callback, uuid, link, config.user_data);
- });
- else
- mvSubmitCallback([=]() {
+ submitCallbackEx([=]() {
PyObject* link = PyTuple_New(2);
PyTuple_SetItem(link, 0, ToPyUUID(node1));
PyTuple_SetItem(link, 1, ToPyUUID(node2));
- mvAddCallback(config.callback, config.alias, link, config.user_data);
- });
+ return link;
+ });
}
}
@@ -367,16 +347,7 @@ void mvNodeEditor::draw(ImDrawList* drawlist, float x, float y)
}
if (_delinkCallback)
{
- if (config.alias.empty())
- mvSubmitCallback([=]() {
- PyObject* link = ToPyUUID(name);
- mvAddCallback(_delinkCallback, uuid, link, config.user_data);
- });
- else
- mvSubmitCallback([=]() {
- PyObject* link = ToPyUUID(name);
- mvAddCallback(_delinkCallback, config.alias, link, config.user_data);
- });
+ submitCallbackEx(_delinkCallback, [=]() { return ToPyUUID(name); });
}
}
diff --git a/src/mvNodes.h b/src/mvNodes.h
index be222911f..f60c9c28b 100644
--- a/src/mvNodes.h
+++ b/src/mvNodes.h
@@ -106,7 +106,7 @@ class mvNodeEditor : public mvAppItem
bool _clearNodes = false;
bool _clearLinks = false;
- PyObject* _delinkCallback = nullptr;
+ mvPyObject _delinkCallback = nullptr;
ImNodesEditorContext* _context = nullptr;
bool _minimap = false;
diff --git a/src/mvPlotting.cpp b/src/mvPlotting.cpp
index 2dae5b92c..6eb507e07 100644
--- a/src/mvPlotting.cpp
+++ b/src/mvPlotting.cpp
@@ -580,27 +580,16 @@ DearPyGui::draw_plot(ImDrawList* drawlist, mvAppItem& item, mvPlotConfig& config
if (item.config.callback != nullptr && query_dirty)
{
- if (item.config.alias.empty()) {
- mvSubmitCallback([=, &item]() {
- PyObject* result = PyTuple_New(config.rects.size());
- for (int i = 0; i < config.rects.size(); ++i) {
- auto rectMin = config.rects[i].Min();
- auto rectMax = config.rects[i].Max();
- PyTuple_SetItem(result, i, Py_BuildValue("(dddd)", rectMin.x, rectMin.y, rectMax.x, rectMax.y));
- }
- mvAddCallback(item.config.callback, item.uuid, result, item.config.user_data);
- });
- } else {
- mvSubmitCallback([=, &item]() {
- PyObject* result = PyTuple_New(config.rects.size());
- for (int i = 0; i < config.rects.size(); ++i) {
- auto rectMin = config.rects[i].Min();
- auto rectMax = config.rects[i].Max();
- PyTuple_SetItem(result, i, Py_BuildValue("(dddd)", rectMin.x, rectMin.y, rectMax.x, rectMax.y));
- }
- mvAddCallback(item.config.callback, item.config.alias, result, item.config.user_data);
- });
- }
+ item.submitCallbackEx([rects=config.rects]() {
+ PyObject* area = PyTuple_New(rects.size());
+ for (int i = 0; i < rects.size(); i++)
+ {
+ auto rectMin = rects[i].Min();
+ auto rectMax = rects[i].Max();
+ PyTuple_SetItem(area, i, Py_BuildValue("(dddd)", rectMin.x, rectMin.y, rectMax.x, rectMax.y));
+ }
+ return area;
+ });
}
if (ImPlot::IsPlotHovered())
@@ -615,15 +604,7 @@ DearPyGui::draw_plot(ImDrawList* drawlist, mvAppItem& item, mvPlotConfig& config
ScopedID id(item.uuid);
if (ImPlot::BeginDragDropTargetPlot())
{
- if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(item.config.payloadType.c_str()))
- {
- auto payloadActual = static_cast(payload->Data);
- if (item.config.alias.empty())
- mvAddCallback(item.config.dropCallback, item.uuid, payloadActual->configData.dragData, nullptr);
- else
- mvAddCallback(item.config.dropCallback, item.config.alias, payloadActual->configData.dragData, nullptr);
- }
-
+ check_drop_event(&item);
ImPlot::EndDragDropTarget();
}
}
@@ -730,12 +711,7 @@ DearPyGui::draw_plot_axis(ImDrawList* drawlist, mvAppItem& item, mvPlotAxisConfi
ScopedID id(item.uuid);
if (ImPlot::BeginDragDropTargetAxis(config.axis))
{
- if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(item.config.payloadType.c_str()))
- {
- auto payloadActual = static_cast(payload->Data);
- mvAddCallback(item.config.dropCallback, item.uuid, payloadActual->configData.dragData, nullptr);
- }
-
+ check_drop_event(&item);
ImPlot::EndDragDropTarget();
}
}
@@ -786,12 +762,7 @@ DearPyGui::draw_plot_legend(ImDrawList* drawlist, mvAppItem& item, mvPlotLegendC
{
if (ImPlot::BeginDragDropTargetLegend())
{
- if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(item.config.payloadType.c_str()))
- {
- auto payloadActual = static_cast(payload->Data);
- mvAddCallback(item.config.dropCallback, item.uuid, payloadActual->configData.dragData, nullptr);
- }
-
+ check_drop_event(&item);
ImPlot::EndDragDropTarget();
}
}
@@ -812,7 +783,7 @@ DearPyGui::draw_drag_line(ImDrawList* drawlist, mvAppItem& item, mvDragLineConfi
{
if (ImPlot::DragLineX(item.uuid, config.value.get(), config.color, config.thickness, config.flags, nullptr, &hovered, &held))
{
- mvAddCallback(item.config.callback, item.uuid, nullptr, item.config.user_data);
+ item.submitCallback();
}
if (config.show_label && !item.config.specifiedLabel.empty() && (hovered || held)) {
char buff[IMPLOT_LABEL_MAX_SIZE];
@@ -828,7 +799,7 @@ DearPyGui::draw_drag_line(ImDrawList* drawlist, mvAppItem& item, mvDragLineConfi
{
if (ImPlot::DragLineY(item.uuid, config.value.get(), config.color, config.thickness, config.flags, nullptr, &hovered, &held))
{
- mvAddCallback(item.config.callback, item.uuid, nullptr, item.config.user_data);
+ item.submitCallback();
}
if (config.show_label && !item.config.specifiedLabel.empty() && (hovered || held)) {
char buff[IMPLOT_LABEL_MAX_SIZE];
@@ -893,7 +864,7 @@ DearPyGui::draw_drag_rect(ImDrawList* drawlist, mvAppItem& item, mvDragRectConfi
(*config.value.get())[1] = ymin;
(*config.value.get())[2] = xmax;
(*config.value.get())[3] = ymax;
- mvAddCallback(item.config.callback, item.uuid, nullptr, item.config.user_data);
+ item.submitCallback();
}
}
@@ -916,7 +887,7 @@ DearPyGui::draw_drag_point(ImDrawList* drawlist, mvAppItem& item, mvDragPointCon
{
(*config.value.get())[0] = dummyx;
(*config.value.get())[1] = dummyy;
- mvAddCallback(item.config.callback, item.uuid, nullptr, item.config.user_data);
+ item.submitCallback();
}
if (config.show_label && !item.config.specifiedLabel.empty() && (hovered || held)) {
ImPlotContext& gp = *GImPlot;
@@ -2319,19 +2290,20 @@ DearPyGui::draw_custom_series(ImDrawList* drawlist, mvAppItem& item, mvCustomSer
}
ImPlotPoint mouse = ImPlot::GetPlotMousePos();
ImVec2 mouse2 = ImPlot::PlotToPixels(mouse.x, mouse.y);
- static int extras = 4;
- mvSubmitCallback([&, mouse, mouse2]() {
+
+ item.submitCallbackEx([=, channelCount=config.channelCount, transformedValues=config._transformedValues] () {
+ const int extras = 4;
PyObject* helperData = PyDict_New();
PyDict_SetItemString(helperData, "MouseX_PlotSpace", ToPyFloat(mouse.x));
PyDict_SetItemString(helperData, "MouseY_PlotSpace", ToPyFloat(mouse.y));
PyDict_SetItemString(helperData, "MouseX_PixelSpace", ToPyFloat(mouse2.x));
PyDict_SetItemString(helperData, "MouseY_PixelSpace", ToPyFloat(mouse2.y));
- PyObject* appData = PyTuple_New(config.channelCount + extras);
+ PyObject* appData = PyTuple_New(channelCount + extras);
PyTuple_SetItem(appData, 0, helperData);
- for (int i = 1; i < config.channelCount + 1; i++)
- PyTuple_SetItem(appData, i, ToPyList(config._transformedValues[i-1]));
- mvAddCallback(item.config.callback, item.uuid, appData, item.config.user_data);
- });
+ for (int i = 0; i < channelCount; i++)
+ PyTuple_SetItem(appData, i + 1, ToPyList(transformedValues[i]));
+ return appData;
+ });
// drawings
ImPlotPlot* currentPlot = ImPlot::GetCurrentContext()->CurrentPlot;
diff --git a/src/mvPyUtils.cpp b/src/mvPyUtils.cpp
index 55edad283..2ddd70a45 100644
--- a/src/mvPyUtils.cpp
+++ b/src/mvPyUtils.cpp
@@ -21,36 +21,29 @@ mvGlobalIntepreterLock::~mvGlobalIntepreterLock()
}
-mvPyObject::mvPyObject(PyObject* rawObject, bool borrowed)
+mvPyObject::mvPyObject(PyObject* rawObject, bool borrowed)
:
- m_rawObject(rawObject),
- m_borrowed(borrowed),
- m_ok(rawObject != nullptr)
+ m_rawObject(rawObject)
{
-
+ if (borrowed)
+ Py_XINCREF(rawObject);
}
mvPyObject::mvPyObject(mvPyObject&& other)
:
- m_rawObject(nullptr),
- m_borrowed(false),
- m_ok(false)
+ m_rawObject(nullptr)
{
std::swap(m_rawObject, other.m_rawObject);
- std::swap(m_borrowed, other.m_borrowed);
- std::swap(m_ok, other.m_ok);
}
mvPyObject& mvPyObject::operator=(mvPyObject&& other)
{
if (this != &other)
{
- if (m_rawObject != nullptr && !m_borrowed)
- Py_XDECREF(m_rawObject);
+ Py_XDECREF(m_rawObject);
+ m_rawObject = nullptr;
std::swap(other.m_rawObject, m_rawObject);
- std::swap(other.m_borrowed, m_borrowed);
- std::swap(other.m_ok, m_ok);
}
return *this;
@@ -58,24 +51,7 @@ mvPyObject& mvPyObject::operator=(mvPyObject&& other)
mvPyObject::~mvPyObject()
{
- if(!m_borrowed && !m_del)
- Py_XDECREF(m_rawObject);
-}
-
-mvPyObject::operator PyObject*()
-{
- return m_rawObject;
-}
-
-void mvPyObject::addRef()
-{
- Py_XINCREF(m_rawObject);
-}
-
-void mvPyObject::delRef()
-{
- Py_XDECREF(m_rawObject);
- m_del = true;
+ Py_XDECREF(m_rawObject);
}
void
@@ -518,6 +494,14 @@ ToPyUUID(mvAppItem* item)
return Py_BuildValue("K", item->uuid);
}
+PyObject*
+ToPyUUID(mvUUID uuid, const std::string& alias)
+{
+ if (alias.empty())
+ return Py_BuildValue("K", uuid);
+ return ToPyString(alias);
+}
+
PyObject*
ToPyUUID(mvUUID value)
{
diff --git a/src/mvPyUtils.h b/src/mvPyUtils.h
index 50cee9927..5be0f6f47 100644
--- a/src/mvPyUtils.h
+++ b/src/mvPyUtils.h
@@ -30,7 +30,12 @@ class mvPyObject
public:
- mvPyObject(PyObject* rawObject, bool borrowed=false);
+ // With `borrowed=false`, the reference is meant for `mvPyObject` to take
+ // ownership of it. `mvPyObject` "steals" the reference from its previous owner;
+ // the reference count is not incremented.
+ // With `borrowed=true`, `mvPyObject` makes its own owned reference by incrementing
+ // the reference count; the original owner keeps ownership too.
+ mvPyObject(PyObject* rawObject, bool borrowed = false);
mvPyObject(mvPyObject&& other);
mvPyObject& operator=(mvPyObject&& other);
@@ -39,18 +44,16 @@ class mvPyObject
~mvPyObject();
- void addRef();
- void delRef();
- bool isOk() const { return m_ok; }
+ bool isOk() const { return (m_rawObject != nullptr); }
- operator PyObject* ();
+ operator PyObject* () const
+ {
+ return m_rawObject;
+ }
private:
PyObject* m_rawObject;
- bool m_borrowed;
- bool m_ok;
- bool m_del = false;
};
@@ -92,6 +95,7 @@ bool isPyObject_Any (PyObject* obj);
PyObject* GetPyNone ();
PyObject* ToPyUUID (mvAppItem* item);
PyObject* ToPyUUID (mvUUID value);
+PyObject* ToPyUUID (mvUUID uuid, const std::string& alias);
PyObject* ToPyLong (long value);
PyObject* ToPyInt (int value);
PyObject* ToPyFloat (float value);
diff --git a/src/mvSlider3D.cpp b/src/mvSlider3D.cpp
index 19eed30d0..b06061610 100644
--- a/src/mvSlider3D.cpp
+++ b/src/mvSlider3D.cpp
@@ -397,14 +397,7 @@ void mvSlider3D::draw(ImDrawList* drawlist, float x, float y)
if(SliderScalar3D(config.specifiedLabel.c_str(), &(*_value)[0], &(*_value)[1], &(*_value)[2], _minX, _maxX, _minY, _maxY, _minZ, _maxZ, _scale))
{
- auto value = *_value;
- mvSubmitCallback([=]() {
-
- if(config.alias.empty())
- mvAddCallback(getCallback(false), uuid, ToPyFloatList(value.data(), (int)value.size()), config.user_data);
- else
- mvAddCallback(getCallback(false), config.alias, ToPyFloatList(value.data(), (int)value.size()), config.user_data);
- });
+ submitCallback(*_value);
}
}
diff --git a/src/mvTables.cpp b/src/mvTables.cpp
index ab2aa4cb6..b02b8ec20 100644
--- a/src/mvTables.cpp
+++ b/src/mvTables.cpp
@@ -279,7 +279,7 @@ void mvTable::draw(ImDrawList* drawlist, float x, float y)
if (sorts_specs->SpecsDirty)
{
if (sorts_specs->SpecsCount == 0)
- mvAddCallback(getCallback(false), uuid, GetPyNone(), config.user_data);
+ submitCallback();
else
{
@@ -295,22 +295,17 @@ void mvTable::draw(ImDrawList* drawlist, float x, float y)
specs.push_back({ idMap[sort_spec->ColumnUserID], sort_spec->SortDirection == ImGuiSortDirection_Ascending ? 1 : -1 });
}
- mvSubmitCallback([=]() {
+ submitCallbackEx([specs=std::move(specs)] () {
PyObject* pySpec = PyList_New(specs.size());
for (size_t i = 0; i < specs.size(); i++)
{
PyObject* pySingleSpec = PyList_New(2);
- PyList_SetItem(pySingleSpec, 0, ToPyLong(specs[i].column));
+ PyList_SetItem(pySingleSpec, 0, Py_BuildValue("K", specs[i].column));
PyList_SetItem(pySingleSpec, 1, ToPyInt(specs[i].direction));
PyList_SetItem(pySpec, i, pySingleSpec);
}
-
- if (config.alias.empty())
- mvRunCallback(getCallback(false), uuid, pySpec, config.user_data);
- else
- mvRunCallback(getCallback(false), config.alias, pySpec, config.user_data);
- Py_XDECREF(pySpec);
- });
+ return pySpec;
+ });
}
sorts_specs->SpecsDirty = false;
}
diff --git a/src/mvTextureItems.cpp b/src/mvTextureItems.cpp
index b52a38cff..3b7c33b73 100644
--- a/src/mvTextureItems.cpp
+++ b/src/mvTextureItems.cpp
@@ -205,19 +205,13 @@ void mvRawTexture::setPyValue(PyObject* value)
}
}
PyBuffer_Release(&buffer_info);
- if (_buffer)
- Py_XDECREF(_buffer);
- Py_XINCREF(value);
- _buffer = value;
+ _buffer = mvPyObject(value, true);
}
}
mvRawTexture::~mvRawTexture()
{
FreeTexture(_texture);
-
- mvGlobalIntepreterLock gil;
- Py_XDECREF(_buffer);
}
void mvRawTexture::draw(ImDrawList* drawlist, float x, float y)
diff --git a/src/mvTextureItems.h b/src/mvTextureItems.h
index e3157c04e..f86093e1e 100644
--- a/src/mvTextureItems.h
+++ b/src/mvTextureItems.h
@@ -71,7 +71,7 @@ class mvRawTexture : public mvAppItem
public:
- PyObject* _buffer = nullptr;
+ mvPyObject _buffer = nullptr;
void* _value = nullptr;
void* _texture = nullptr;
bool _dirty = true;
diff --git a/src/mvTimePicker.cpp b/src/mvTimePicker.cpp
index be8959a22..8bfeb0c2e 100644
--- a/src/mvTimePicker.cpp
+++ b/src/mvTimePicker.cpp
@@ -69,13 +69,7 @@ void mvTimePicker::draw(ImDrawList* drawlist, float x, float y)
{
ImPlot::GetGmtTime(*_imvalue, _value.get());
{
- auto value = *_value;
- mvSubmitCallback([=]() {
- if(config.alias.empty())
- mvAddCallback(getCallback(false), uuid, ToPyTime(value), config.user_data);
- else
- mvAddCallback(getCallback(false), config.alias, ToPyTime(value), config.user_data);
- });
+ submitCallback(*_value);
}
}
}
diff --git a/src/mvViewport.h b/src/mvViewport.h
index 52e194b51..b61b484ba 100644
--- a/src/mvViewport.h
+++ b/src/mvViewport.h
@@ -65,13 +65,24 @@ void mvToggleFullScreen(mvViewport& viewport);
static void mvOnResize()
{
- mvSubmitCallback([=]() {
- PyObject* dimensions = PyTuple_New(4);
- PyTuple_SetItem(dimensions, 0, PyLong_FromLong(GContext->viewport->actualWidth));
- PyTuple_SetItem(dimensions, 1, PyLong_FromLong(GContext->viewport->actualHeight));
- PyTuple_SetItem(dimensions, 2, PyLong_FromLong(GContext->viewport->clientWidth));
- PyTuple_SetItem(dimensions, 3, PyLong_FromLong(GContext->viewport->clientHeight));
- mvAddCallback(
- GContext->callbackRegistry->resizeCallback, MV_APP_UUID, dimensions, GContext->callbackRegistry->resizeCallbackUserData);
- });
+ auto v = GContext->viewport;
+ mvAddOwnerlessCallback(
+ GContext->callbackRegistry->resizeCallback,
+ GContext->callbackRegistry->resizeCallbackUserData,
+ MV_APP_UUID, "",
+ [
+ actualWidth = v->actualWidth,
+ actualHeight = v->actualHeight,
+ clientWidth = v->clientWidth,
+ clientHeight = v->clientHeight
+ ]
+ () {
+ PyObject* dimensions = PyTuple_New(4);
+ PyTuple_SetItem(dimensions, 0, PyLong_FromLong(actualWidth));
+ PyTuple_SetItem(dimensions, 1, PyLong_FromLong(actualHeight));
+ PyTuple_SetItem(dimensions, 2, PyLong_FromLong(clientWidth));
+ PyTuple_SetItem(dimensions, 3, PyLong_FromLong(clientHeight));
+ return dimensions;
+ }
+ );
}
\ No newline at end of file
diff --git a/src/mvViewport_apple.mm b/src/mvViewport_apple.mm
index e6e41cf2d..819cac2d9 100644
--- a/src/mvViewport_apple.mm
+++ b/src/mvViewport_apple.mm
@@ -22,12 +22,10 @@
window_close_callback(GLFWwindow* window)
{
if (GContext->viewport->disableClose) {
- mvSubmitCallback([=]() {
- mvRunCallback(GContext->callbackRegistry->onCloseCallback, 0, nullptr, GContext->callbackRegistry->onCloseCallbackUserData);
- });
+ mvAddOwnerlessCallback(GContext->callbackRegistry->onCloseCallback, GContext->callbackRegistry->onCloseCallbackUserData);
}
else {
- GContext->started = false;
+ StopRendering();
}
}
diff --git a/src/mvViewport_linux.cpp b/src/mvViewport_linux.cpp
index b8b6496e3..31f7b7f87 100644
--- a/src/mvViewport_linux.cpp
+++ b/src/mvViewport_linux.cpp
@@ -21,12 +21,10 @@ static void
window_close_callback(GLFWwindow* window)
{
if (GContext->viewport->disableClose) {
- mvSubmitCallback([=]() {
- mvRunCallback(GContext->callbackRegistry->onCloseCallback, 0, nullptr, GContext->callbackRegistry->onCloseCallbackUserData);
- });
+ mvAddOwnerlessCallback(GContext->callbackRegistry->onCloseCallback, GContext->callbackRegistry->onCloseCallbackUserData);
}
else {
- GContext->started = false;
+ StopRendering();
}
}
@@ -125,7 +123,7 @@ mvCleanupViewport(mvViewport& viewport)
glfwDestroyWindow(viewportData->handle);
glfwTerminate();
- GContext->started = false;
+ StopRendering();
delete viewportData;
viewportData = nullptr;
diff --git a/src/mvViewport_win32.cpp b/src/mvViewport_win32.cpp
index 0e5a29af8..a8e13a00e 100644
--- a/src/mvViewport_win32.cpp
+++ b/src/mvViewport_win32.cpp
@@ -276,12 +276,10 @@ mvHandleMsg(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept
break;
case WM_CLOSE:
if (GContext->viewport->disableClose) {
- mvSubmitCallback([=]() {
- mvRunCallback(GContext->callbackRegistry->onCloseCallback, 0, nullptr, GContext->callbackRegistry->onCloseCallbackUserData);
- });
+ mvAddOwnerlessCallback(GContext->callbackRegistry->onCloseCallback, GContext->callbackRegistry->onCloseCallbackUserData);
return 0;
}
- GContext->started = false;
+ StopRendering();
DestroyWindow(hWnd);
::PostQuitMessage(0);
return 0;
From 49cb28132ccfd89eab92ccd2a06093617dc6139e Mon Sep 17 00:00:00 2001
From: Vladimir Ein
Date: Wed, 17 Dec 2025 02:47:45 +0500
Subject: [PATCH 27/58] fix: Releasing GIL before attempts to lock the mutex
#2053
---
CMakeLists.txt | 11 ++
src/dearpygui_commands.h | 237 ++++++++++++++++++++-------------------
src/mvContext.cpp | 4 +-
src/mvFontManager.cpp | 4 +
src/mvItemRegistry.cpp | 10 +-
src/mvLayoutWindow.cpp | 3 +
src/mvPyUtils.h | 43 +++++++
src/mvToolWindow.cpp | 2 +-
src/mvViewport_apple.mm | 38 +++++--
src/mvViewport_linux.cpp | 2 +
src/mvViewport_win32.cpp | 10 +-
11 files changed, 226 insertions(+), 138 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a86797350..adb1bce8d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -48,6 +48,17 @@ if(MV_TESTS_ONLY)
add_definitions(-DMV_TESTS_ONLY)
endif()
+# Specifying MV_NO_USER_THREADS turns off some thread safety features (might give
+# you some perf gain, yeah, those 0.01%) and must only be used when no user threads
+# (threading.Thread) **ever** call DPG API. With MV_NO_USER_THREADS, it's only
+# allowed to call DPG from the main thread and from handlers/callbacks.
+# Also can be used to get back the behavior of old DPG versions that were thread
+# unsafe (for testing/debugging).
+set(MV_NO_USER_THREADS ${MV_NO_USER_THREADS})
+if(MV_NO_USER_THREADS)
+ add_definitions(-DMV_NO_USER_THREADS)
+endif()
+
add_subdirectory("thirdparty")
# if this is not a distribution build
diff --git a/src/dearpygui_commands.h b/src/dearpygui_commands.h
index 4b68a149e..cc81f4bf7 100644
--- a/src/dearpygui_commands.h
+++ b/src/dearpygui_commands.h
@@ -26,7 +26,7 @@ bind_colormap(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["bind_colormap"], args, kwargs, __FUNCTION__, &itemraw, &sourceraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
mvUUID source = GetIDFromPyObject(sourceraw);
@@ -102,7 +102,7 @@ sample_colormap(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["sample_colormap"], args, kwargs, __FUNCTION__, &itemraw, &t))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -144,7 +144,7 @@ get_colormap_color(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["get_colormap_color"], args, kwargs, __FUNCTION__, &itemraw, &index))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -179,7 +179,7 @@ get_file_dialog_info(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["get_file_dialog_info"], args, kwargs, __FUNCTION__, &file_dialog_raw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID file_dialog = GetIDFromPyObject(file_dialog_raw);
@@ -212,7 +212,7 @@ set_x_scroll(PyObject* self, PyObject* args, PyObject* kwargs)
&itemraw, &value))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -264,7 +264,7 @@ set_y_scroll(PyObject* self, PyObject* args, PyObject* kwargs)
&itemraw, &value))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -315,7 +315,7 @@ get_x_scroll(PyObject* self, PyObject* args, PyObject* kwargs)
&itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -365,7 +365,7 @@ get_y_scroll(PyObject* self, PyObject* args, PyObject* kwargs)
&itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -415,7 +415,7 @@ get_x_scroll_max(PyObject* self, PyObject* args, PyObject* kwargs)
&itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -465,7 +465,7 @@ get_y_scroll_max(PyObject* self, PyObject* args, PyObject* kwargs)
&itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -520,7 +520,7 @@ set_clip_space(PyObject* self, PyObject* args, PyObject* kwargs)
&topleftx, &toplefty, &width, &height, &mindepth, &maxdepth))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -570,7 +570,7 @@ apply_transform(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["apply_transform"], args, kwargs, __FUNCTION__, &itemraw, &transform))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -611,7 +611,7 @@ create_rotation_matrix(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["create_rotation_matrix"], args, kwargs, __FUNCTION__, &angle, &axis))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvVec4 aaxis = ToVec4(axis);
@@ -637,7 +637,7 @@ create_perspective_matrix(PyObject* self, PyObject* args, PyObject* kwargs)
&fov, &aspect, &zNear, &zFar))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
PyObject* newbuffer = nullptr;
PymvMat4* newbufferview = nullptr;
@@ -663,7 +663,7 @@ create_orthographic_matrix(PyObject* self, PyObject* args, PyObject* kwargs)
&left, &right, &bottom, &top, &zNear, &zFar))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
PyObject* newbuffer = nullptr;
PymvMat4* newbufferview = nullptr;
@@ -684,7 +684,7 @@ create_translation_matrix(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["create_translation_matrix"], args, kwargs, __FUNCTION__, &axis))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvVec4 aaxis = ToVec4(axis);
@@ -707,7 +707,7 @@ create_scale_matrix(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["create_scale_matrix"], args, kwargs, __FUNCTION__, &axis))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvVec4 aaxis = ToVec4(axis);
@@ -733,7 +733,7 @@ create_lookat_matrix(PyObject* self, PyObject* args, PyObject* kwargs)
&eye, ¢er, &up))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvVec4 aeye = ToVec4(eye);
mvVec4 acenter = ToVec4(center);
@@ -761,7 +761,7 @@ create_fps_matrix(PyObject* self, PyObject* args, PyObject* kwargs)
&eye, &pitch, &yaw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvVec4 aeye = ToVec4(eye);
PyObject* newbuffer = nullptr;
@@ -784,7 +784,7 @@ bind_font(PyObject* self, PyObject* args, PyObject* kwargs)
&itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -830,7 +830,7 @@ get_text_size(PyObject* self, PyObject* args, PyObject* kwargs)
&text, &wrap_width, &fontRaw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID font = GetIDFromPyObject(fontRaw);
@@ -885,7 +885,7 @@ get_selected_nodes(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["get_selected_nodes"], args, kwargs, __FUNCTION__, &node_editor_raw))
return ToPyBool(false);
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID node_editor = GetIDFromPyObject(node_editor_raw);
@@ -920,7 +920,7 @@ get_selected_links(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["get_selected_links"], args, kwargs, __FUNCTION__, &node_editor_raw))
return ToPyBool(false);
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID node_editor = GetIDFromPyObject(node_editor_raw);
@@ -954,7 +954,7 @@ clear_selected_links(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["clear_selected_links"], args, kwargs, __FUNCTION__, &node_editor_raw))
return ToPyBool(false);
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID node_editor = GetIDFromPyObject(node_editor_raw);
@@ -988,7 +988,7 @@ clear_selected_nodes(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["clear_selected_nodes"], args, kwargs, __FUNCTION__, &node_editor_raw))
return ToPyBool(false);
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID node_editor = GetIDFromPyObject(node_editor_raw);
@@ -1023,7 +1023,7 @@ get_plot_query_rects(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())[tag], args, kwargs, __FUNCTION__, &plotraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID plot = GetIDFromPyObject(plotraw);
@@ -1063,7 +1063,7 @@ set_axis_ticks(PyObject* self, PyObject* args, PyObject* kwargs)
auto mlabel_pairs = ToVectPairStringFloat(label_pairs);
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID plot = GetIDFromPyObject(plotraw);
@@ -1116,7 +1116,7 @@ set_axis_limits_constraints(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())[tag], args, kwargs, __FUNCTION__, &axisraw, &vmin, &vmax))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID axis = GetIDFromPyObject(axisraw);
@@ -1150,7 +1150,7 @@ reset_axis_limits_constraints(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())[tag], args, kwargs, __FUNCTION__, &axisraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID axis = GetIDFromPyObject(axisraw);
@@ -1185,7 +1185,7 @@ set_axis_zoom_constraints(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())[tag], args, kwargs, __FUNCTION__, &axisraw, &vmin, &vmax))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID axis = GetIDFromPyObject(axisraw);
@@ -1220,7 +1220,7 @@ reset_axis_zoom_constraints(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())[tag], args, kwargs, __FUNCTION__, &axisraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID axis = GetIDFromPyObject(axisraw);
@@ -1255,7 +1255,7 @@ set_axis_limits(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["set_axis_limits"], args, kwargs, __FUNCTION__, &axisraw, &ymin, &ymax))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID axis = GetIDFromPyObject(axisraw);
@@ -1288,7 +1288,7 @@ set_axis_limits_auto(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["set_axis_limits_auto"], args, kwargs, __FUNCTION__, &axisraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID axis = GetIDFromPyObject(axisraw);
@@ -1322,7 +1322,7 @@ fit_axis_data(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["fit_axis_data"], args, kwargs, __FUNCTION__, &axisraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID axis = GetIDFromPyObject(axisraw);
@@ -1358,7 +1358,7 @@ get_axis_limits(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["get_axis_limits"], args, kwargs, __FUNCTION__, &plotraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID plot = GetIDFromPyObject(plotraw);
@@ -1391,7 +1391,7 @@ reset_axis_ticks(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["reset_axis_ticks"], args, kwargs, __FUNCTION__, &plotraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID plot = GetIDFromPyObject(plotraw);
@@ -1429,7 +1429,7 @@ highlight_table_column(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["highlight_table_column"], args, kwargs, __FUNCTION__, &tableraw, &column, &color))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID table = GetIDFromPyObject(tableraw);
@@ -1473,7 +1473,7 @@ unhighlight_table_column(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["unhighlight_table_column"], args, kwargs, __FUNCTION__, &tableraw, &column))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID table = GetIDFromPyObject(tableraw);
@@ -1516,7 +1516,7 @@ set_table_row_color(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["set_table_row_color"], args, kwargs, __FUNCTION__, &tableraw, &row, &color))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID table = GetIDFromPyObject(tableraw);
@@ -1560,7 +1560,7 @@ unset_table_row_color(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["unset_table_row_color"], args, kwargs, __FUNCTION__, &tableraw, &row))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID table = GetIDFromPyObject(tableraw);
@@ -1602,7 +1602,7 @@ highlight_table_row(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["highlight_table_row"], args, kwargs, __FUNCTION__, &tableraw, &row, &color))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID table = GetIDFromPyObject(tableraw);
@@ -1646,7 +1646,7 @@ unhighlight_table_row(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["unhighlight_table_row"], args, kwargs, __FUNCTION__, &tableraw, &row))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID table = GetIDFromPyObject(tableraw);
@@ -1690,7 +1690,7 @@ highlight_table_cell(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["highlight_table_cell"], args, kwargs, __FUNCTION__, &tableraw, &row, &column, &color))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID table = GetIDFromPyObject(tableraw);
@@ -1735,7 +1735,7 @@ unhighlight_table_cell(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["unhighlight_table_cell"], args, kwargs, __FUNCTION__, &tableraw, &row, &column))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID table = GetIDFromPyObject(tableraw);
@@ -1778,7 +1778,7 @@ is_table_cell_highlighted(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["is_table_cell_highlighted"], args, kwargs, __FUNCTION__, &tableraw, &row, &column))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID table = GetIDFromPyObject(tableraw);
@@ -1825,7 +1825,7 @@ is_table_row_highlighted(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["is_table_row_highlighted"], args, kwargs, __FUNCTION__, &tableraw, &row))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID table = GetIDFromPyObject(tableraw);
@@ -1865,7 +1865,7 @@ is_table_column_highlighted(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["is_table_column_highlighted"], args, kwargs, __FUNCTION__, &tableraw, &column))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID table = GetIDFromPyObject(tableraw);
@@ -1906,7 +1906,7 @@ bind_theme(PyObject* self, PyObject* args, PyObject* kwargs)
&itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -1949,7 +1949,7 @@ set_global_font_scale(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["set_global_font_scale"], args, kwargs, __FUNCTION__, &scale))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvToolManager::GetFontManager().setGlobalFontScale(scale);
return GetPyNone();
@@ -1987,7 +1987,7 @@ set_decimal_point(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["set_decimal_point"], args, kwargs, __FUNCTION__, &point, &from_locale))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
GContext->IO.decimalPoint = *point;
ImGui::GetIO().PlatformLocaleDecimalPoint = GContext->IO.decimalPoint;
@@ -2006,7 +2006,7 @@ set_frame_callback(PyObject* self, PyObject* args, PyObject* kwargs)
&frame, &callback, &user_data))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
if (frame > GContext->callbackRegistry->highestFrame)
GContext->callbackRegistry->highestFrame = frame;
@@ -2053,7 +2053,7 @@ static PyObject*
get_viewport_configuration(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
PyObject* pdict = PyDict_New();
@@ -2090,7 +2090,7 @@ static PyObject*
is_viewport_ok(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvViewport* viewport = GContext->viewport;
if (viewport)
@@ -2185,7 +2185,8 @@ show_viewport(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
configure_viewport(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
+
mvViewport* viewport = GContext->viewport;
if (viewport)
{
@@ -2218,7 +2219,7 @@ configure_viewport(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
maximize_viewport(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvSubmitTask([=]()
{
mvMaximizeViewport(*GContext->viewport);
@@ -2230,7 +2231,7 @@ maximize_viewport(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
minimize_viewport(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvSubmitTask([=]()
{
mvMinimizeViewport(*GContext->viewport);
@@ -2242,7 +2243,7 @@ minimize_viewport(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
toggle_viewport_fullscreen(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvSubmitTask([=]()
{
mvToggleFullScreen(*GContext->viewport);
@@ -2290,7 +2291,13 @@ split_frame(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
lock_mutex(PyObject* self, PyObject* args, PyObject* kwargs)
{
+ // Since we may enter waiting state on mutex.lock(), we must release the
+ // GIL while attempting to lock the mutex; otherwise, we'd risk getting
+ // into a deadlock.
+ Py_BEGIN_ALLOW_THREADS;
GContext->mutex.lock();
+ Py_END_ALLOW_THREADS;
+
return GetPyNone();
}
@@ -2304,7 +2311,7 @@ unlock_mutex(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
get_frame_count(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
return ToPyInt(GContext->frame);
}
@@ -2570,9 +2577,9 @@ static PyObject*
setup_dearpygui(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
-
Py_BEGIN_ALLOW_THREADS;
+ std::lock_guard lk(GContext->mutex);
+
if (GContext->started)
{
@@ -2673,7 +2680,7 @@ destroy_context(PyObject* self, PyObject* args, PyObject* kwargs)
// Even though the handlers thread is down, there's still a chance that
// the user calls DPG from another Python thread. We'd better lock the
// mutex while we're tinkering with all the global structures.
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvToolManager::Reset();
ClearItemRegistry(*GContext->itemRegistry);
@@ -2705,7 +2712,7 @@ static PyObject*
stop_dearpygui(PyObject* self, PyObject* args, PyObject* kwargs)
{
StopRendering();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
auto viewport = GContext->viewport;
if (viewport)
viewport->running = false;
@@ -2715,14 +2722,14 @@ stop_dearpygui(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
get_total_time(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
return ToPyFloat((f32)GContext->time);
}
static PyObject*
get_delta_time(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
return ToPyFloat(GContext->deltaTime);
}
@@ -2730,7 +2737,7 @@ get_delta_time(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
get_frame_rate(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
return ToPyFloat((f32)GContext->framerate);
}
@@ -2757,7 +2764,7 @@ configure_app(PyObject* self, PyObject* args, PyObject* kwargs)
return GetPyNone();
}
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
if (PyObject* item = PyDict_GetItemString(kwargs, "auto_device")) GContext->IO.info_auto_device = ToBool(item);
if (PyObject* item = PyDict_GetItemString(kwargs, "docking")) GContext->IO.docking = ToBool(item);
@@ -2796,7 +2803,7 @@ configure_app(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
get_app_configuration(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
PyObject* pdict = PyDict_New();
PyDict_SetItemString(pdict, "auto_device", mvPyObject(ToPyBool(GContext->IO.info_auto_device)));
PyDict_SetItemString(pdict, "docking", mvPyObject(ToPyBool(GContext->IO.docking)));
@@ -2972,7 +2979,7 @@ static PyObject*
pop_container_stack(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
if (GContext->itemRegistry->containers.empty())
{
@@ -2994,7 +3001,7 @@ pop_container_stack(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
empty_container_stack(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
while (!GContext->itemRegistry->containers.empty())
GContext->itemRegistry->containers.pop();
return GetPyNone();
@@ -3003,7 +3010,7 @@ empty_container_stack(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
top_container_stack(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvAppItem* item = nullptr;
if (!GContext->itemRegistry->containers.empty())
@@ -3018,7 +3025,7 @@ top_container_stack(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
last_item(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
return ToPyUUID(GContext->itemRegistry->lastItemAdded);
}
@@ -3026,7 +3033,7 @@ last_item(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
last_container(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
return ToPyUUID(GContext->itemRegistry->lastContainerAdded);
}
@@ -3034,7 +3041,7 @@ last_container(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
last_root(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
return ToPyUUID(GContext->itemRegistry->lastRootAdded);
}
@@ -3047,7 +3054,7 @@ push_container_stack(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["push_container_stack"], args, kwargs, __FUNCTION__, &itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -3075,7 +3082,7 @@ set_primary_window(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["set_primary_window"], args, kwargs, __FUNCTION__, &itemraw, &value))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -3153,7 +3160,7 @@ set_primary_window(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
get_active_window(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
return ToPyUUID(GContext->activeWindow);
}
@@ -3161,7 +3168,7 @@ get_active_window(PyObject* self, PyObject* args, PyObject* kwargs)
static PyObject*
get_focused_item(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
return ToPyUUID(GContext->focusedItem);
}
@@ -3178,7 +3185,7 @@ move_item(PyObject* self, PyObject* args, PyObject* kwargs)
&itemraw, &parentraw, &beforeraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
mvUUID parent = GetIDFromPyObject(parentraw);
@@ -3200,7 +3207,7 @@ delete_item(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["delete_item"], args, kwargs, __FUNCTION__, &itemraw, &childrenOnly, &slot))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -3220,7 +3227,7 @@ does_item_exist(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["does_item_exist"], args, kwargs, __FUNCTION__, &itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -3238,7 +3245,7 @@ move_item_up(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["move_item_up"], args, kwargs, __FUNCTION__, &itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -3257,7 +3264,7 @@ move_item_down(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["move_item_down"], args, kwargs, __FUNCTION__, &itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -3278,7 +3285,7 @@ reorder_items(PyObject* self, PyObject* args, PyObject* kwargs)
&containerraw, &slot, &new_order))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
auto anew_order = ToUUIDVect(new_order);
mvUUID container = GetIDFromPyObject(containerraw);
@@ -3315,7 +3322,7 @@ unstage(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["unstage"], args, kwargs, __FUNCTION__, &itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -3357,7 +3364,7 @@ show_item_debug(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["show_item_debug"], args, kwargs, __FUNCTION__, &itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -3420,7 +3427,7 @@ static PyObject*
get_all_items(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
std::vector childList;
@@ -3445,7 +3452,7 @@ static PyObject*
show_imgui_demo(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
GContext->itemRegistry->showImGuiDebug = true;
return GetPyNone();
@@ -3455,7 +3462,7 @@ static PyObject*
show_implot_demo(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
GContext->itemRegistry->showImPlotDebug = true;
return GetPyNone();
@@ -3465,7 +3472,7 @@ static PyObject*
get_windows(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
std::vector childList;
for (auto& root : GContext->itemRegistry->colormapRoots) childList.emplace_back(root->uuid);
@@ -3495,7 +3502,7 @@ add_alias(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["add_alias"], args, kwargs, __FUNCTION__, &alias, &itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -3514,7 +3521,7 @@ remove_alias(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["remove_alias"], args, kwargs, __FUNCTION__, &alias))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
RemoveAlias((*GContext->itemRegistry), alias);
@@ -3531,7 +3538,7 @@ does_alias_exist(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["does_alias_exist"], args, kwargs, __FUNCTION__, &alias))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
bool result = GContext->itemRegistry->aliases.count(alias) != 0;
@@ -3547,7 +3554,7 @@ get_alias_id(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["get_alias_id"], args, kwargs, __FUNCTION__, &alias))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID result = GetIdFromAlias((*GContext->itemRegistry), alias);
@@ -3558,7 +3565,7 @@ static PyObject*
get_aliases(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
std::vector aliases;
@@ -3576,7 +3583,7 @@ focus_item(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["focus_item"], args, kwargs, __FUNCTION__, &itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
@@ -3649,7 +3656,7 @@ get_item_info(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["get_item_info"], args, kwargs, __FUNCTION__, &itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
mvAppItem* appitem = GetItem((*GContext->itemRegistry), item);
@@ -3734,7 +3741,7 @@ get_item_configuration(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["get_item_configuration"], args, kwargs, __FUNCTION__, &itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
mvAppItem* appitem = GetItem((*GContext->itemRegistry), item);
@@ -3802,7 +3809,7 @@ set_item_children(PyObject* self, PyObject* args, PyObject* kwargs)
&itemraw, &sourceraw, &slot))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
mvUUID source = GetIDFromPyObject(sourceraw);
@@ -3873,7 +3880,7 @@ bind_item_font(PyObject* self, PyObject* args, PyObject* kwargs)
&itemraw, &fontraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
mvUUID font = GetIDFromPyObject(fontraw);
@@ -3915,7 +3922,7 @@ bind_item_theme(PyObject* self, PyObject* args, PyObject* kwargs)
&itemraw, &themeraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
mvUUID theme = GetIDFromPyObject(themeraw);
@@ -3962,7 +3969,7 @@ bind_item_handler_registry(PyObject* self, PyObject* args, PyObject* kwargs)
&itemraw, ®raw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
mvUUID reg = GetIDFromPyObject(regraw);
@@ -4009,7 +4016,7 @@ reset_pos(PyObject* self, PyObject* args, PyObject* kwargs)
&itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
mvAppItem* appitem = GetItem((*GContext->itemRegistry), item);
@@ -4031,7 +4038,7 @@ get_item_state(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["get_item_state"], args, kwargs, __FUNCTION__, &itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
mvAppItem* appitem = GetItem((*GContext->itemRegistry), item);
@@ -4051,7 +4058,7 @@ static PyObject*
get_item_types(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
PyObject* pdict = PyDict_New();
#define X(el) PyDict_SetItemString(pdict, #el, PyLong_FromLong((int)mvAppItemType::el));
@@ -4065,7 +4072,7 @@ static PyObject*
configure_item(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(PyTuple_GetItem(args, 0));
mvAppItem* appitem = GetItem((*GContext->itemRegistry), item);
@@ -4090,7 +4097,7 @@ get_value(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["get_value"], args, kwargs, __FUNCTION__, &nameraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID name = GetIDFromPyObject(nameraw);
mvAppItem* item = GetItem(*GContext->itemRegistry, name);
@@ -4108,7 +4115,7 @@ get_values(PyObject* self, PyObject* args, PyObject* kwargs)
if (!Parse((GetParsers())["get_values"], args, kwargs, __FUNCTION__, &items))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
auto aitems = ToUUIDVect(items);
PyObject* pyvalues = PyList_New(aitems.size());
@@ -4141,7 +4148,7 @@ set_value(PyObject* self, PyObject* args, PyObject* kwargs)
if (value)
Py_XINCREF(value);
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID name = GetIDFromPyObject(nameraw);
@@ -4169,7 +4176,7 @@ set_item_alias(PyObject* self, PyObject* args, PyObject* kwargs)
&itemraw, &alias))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
mvAppItem* appitem = GetItem((*GContext->itemRegistry), item);
@@ -4187,7 +4194,7 @@ get_item_alias(PyObject* self, PyObject* args, PyObject* kwargs)
&itemraw))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
mvUUID item = GetIDFromPyObject(itemraw);
mvAppItem* appitem = GetItem((*GContext->itemRegistry), item);
@@ -4206,7 +4213,7 @@ capture_next_item(PyObject* self, PyObject* args, PyObject* kwargs)
&callable, &user_data))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
GContext->itemRegistry->captureCallback = mvPyObject(callable == Py_None? nullptr : callable, true);
GContext->itemRegistry->captureCallbackUserData = mvPyObject(user_data, true);
@@ -4220,7 +4227,7 @@ get_callback_queue(PyObject* self, PyObject* args, PyObject* kwargs)
if (GContext->callbackRegistry->jobs.empty())
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
PyObject* pArgs = PyTuple_New(GContext->callbackRegistry->jobs.size());
for (int i = 0; i < GContext->callbackRegistry->jobs.size(); i++)
@@ -4276,7 +4283,7 @@ set_clipboard_text(PyObject* self, PyObject* args, PyObject* kwargs)
&text))
return GetPyNone();
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
ImGui::SetClipboardText(text);
@@ -4287,7 +4294,7 @@ static PyObject*
get_clipboard_text(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
const char* text = ImGui::GetClipboardText();
@@ -4298,7 +4305,7 @@ static PyObject*
get_platform(PyObject* self, PyObject* args, PyObject* kwargs)
{
- std::lock_guard lk(GContext->mutex);
+ mvPySafeLockGuard lk(GContext->mutex);
#ifdef _WIN32
return ToPyInt(0L);
diff --git a/src/mvContext.cpp b/src/mvContext.cpp
index 586714880..cad61d205 100644
--- a/src/mvContext.cpp
+++ b/src/mvContext.cpp
@@ -160,6 +160,9 @@ SetDefaultTheme()
void
Render()
{
+ // We lock the mutex from the very start so that frame count is kept valid
+ // for API calls.
+ std::lock_guard lk(GContext->mutex);
// update timing
GContext->deltaTime = ImGui::GetIO().DeltaTime;
@@ -180,7 +183,6 @@ Render()
mvToolManager::Draw();
{
- std::lock_guard lk(GContext->mutex);
if (GContext->resetTheme)
{
SetDefaultTheme();
diff --git a/src/mvFontManager.cpp b/src/mvFontManager.cpp
index 8ff88fa94..a6e2e6437 100644
--- a/src/mvFontManager.cpp
+++ b/src/mvFontManager.cpp
@@ -168,6 +168,10 @@ mvFontManager::rebuildAtlas()
{
item->customAction(nullptr);
}
+
+ // Just to make sure g.Font doesn't point to a font already deleted by
+ // io.Fonts->Clear(), though ideally ImGui should be doing it on its own.
+ ImGui::SetCurrentFont(ImGui::GetDefaultFont());
}
_dirty = false;
diff --git a/src/mvItemRegistry.cpp b/src/mvItemRegistry.cpp
index 07e403511..376307159 100644
--- a/src/mvItemRegistry.cpp
+++ b/src/mvItemRegistry.cpp
@@ -1280,9 +1280,6 @@ AddItemWithRuntimeChecks(mvItemRegistry& registry, std::shared_ptr