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 @@

- static-analysis - static-analysis + static-analysis + static-analysis Deployment Documentation Status

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 it return true; } - if (DearPyGui::GetEntityDesciptionFlags(item->type) & MV_ITEM_DESC_HANDLER && parent == 0) - parent = item->config.parent; - if (item == nullptr) return false; @@ -1290,6 +1287,11 @@ AddItemWithRuntimeChecks(mvItemRegistry& registry, std::shared_ptr it if (!item->state.ok) return false; + mvPySafeLockGuard lk(GContext->mutex); + + if (DearPyGui::GetEntityDesciptionFlags(item->type) & MV_ITEM_DESC_HANDLER && parent == 0) + parent = item->config.parent; + //--------------------------------------------------------------------------- // STEP 0: updata "last" information //--------------------------------------------------------------------------- @@ -1322,8 +1324,6 @@ AddItemWithRuntimeChecks(mvItemRegistry& registry, std::shared_ptr it }; AddTechnique technique = AddTechnique::NONE; - std::lock_guard lk(GContext->mutex); - //--------------------------------------------------------------------------- // STEP 2: handle root case //--------------------------------------------------------------------------- diff --git a/src/mvLayoutWindow.cpp b/src/mvLayoutWindow.cpp index d528542a5..78bec1d96 100644 --- a/src/mvLayoutWindow.cpp +++ b/src/mvLayoutWindow.cpp @@ -137,6 +137,7 @@ void mvLayoutWindow::drawWidgets() std::lock_guard lk(GContext->mutex); mvSubmitCallback([&]() { + mvPySafeLockGuard lk(GContext->mutex); MoveItemUp(*GContext->itemRegistry, m_selectedItem); }); } @@ -147,6 +148,7 @@ void mvLayoutWindow::drawWidgets() std::lock_guard lk(GContext->mutex); mvSubmitCallback([&]() { + mvPySafeLockGuard lk(GContext->mutex); MoveItemDown(*GContext->itemRegistry, m_selectedItem); }); } @@ -156,6 +158,7 @@ void mvLayoutWindow::drawWidgets() std::lock_guard lk(GContext->mutex); mvSubmitCallback([&]() { + mvPySafeLockGuard lk(GContext->mutex); DeleteItem(*GContext->itemRegistry, m_selectedItem, false); m_selectedItem = 0; }); diff --git a/src/mvPyUtils.h b/src/mvPyUtils.h index 5be0f6f47..5e83cbd38 100644 --- a/src/mvPyUtils.h +++ b/src/mvPyUtils.h @@ -25,6 +25,49 @@ struct mvGlobalIntepreterLock }; +// Note: the caller MUST hold GIL during instantiation of this class. +// This class is similar to std::lock_guard, except that it releases GIL +// before attempting to lock the mutex, and acquires GIL again once it +// succeeds with mutex lock. For the code using it, it works exactly like +// std::lock_guard, but prevents potential deadlocks between threads using Python. +template +class mvPySafeLockGuard +{ +public: + explicit mvPySafeLockGuard(MutexType &mutex) + : _mutex(mutex) + { +#ifdef MV_NO_USER_THREADS + + // Technically, for MV_NO_USER_THREADS we could simply declare mvPySafeLockGuard + // to be an alias of std::lock_guard. However, on pre-C++20 compilers this + // would require explicit specification of template arguments because argument + // deduction on alias templates was disallowed back then. This would make + // the code bulky, so let's just reimplement std::lock here so that MutexType + // can be deduced. + mutex.lock(); + +#else // !MV_NO_USER_THREADS + + Py_BEGIN_ALLOW_THREADS; + mutex.lock(); + Py_END_ALLOW_THREADS; + +#endif // !MV_NO_USER_THREADS + } + + ~mvPySafeLockGuard() noexcept + { + _mutex.unlock(); + } + + mvPySafeLockGuard(const mvPySafeLockGuard&) = delete; + mvPySafeLockGuard &operator=(const mvPySafeLockGuard&) = delete; + +private: + MutexType& _mutex; +}; + class mvPyObject { diff --git a/src/mvToolWindow.cpp b/src/mvToolWindow.cpp index 236d1696b..2914b4c2d 100644 --- a/src/mvToolWindow.cpp +++ b/src/mvToolWindow.cpp @@ -38,7 +38,7 @@ void mvToolWindow::draw() GContext->input.mousePos.x = (int)x; GContext->input.mousePos.y = (int)y; - std::lock_guard lk(GContext->mutex); + std::lock_guard lk(GContext->mutex); GContext->activeWindow = getUUID(); diff --git a/src/mvViewport_apple.mm b/src/mvViewport_apple.mm index 819cac2d9..8d6011c05 100644 --- a/src/mvViewport_apple.mm +++ b/src/mvViewport_apple.mm @@ -212,12 +212,16 @@ else glfwPollEvents(); - if (mvToolManager::GetFontManager().isInvalid()) { - mvToolManager::GetFontManager().rebuildAtlas(); - ImGui_ImplMetal_DestroyFontsTexture(); - mvToolManager::GetFontManager().updateAtlas(); - ImGui_ImplMetal_CreateFontsTexture(graphicsData->device); + // Font manager is thread-unsafe, so we'd better sync it + std::lock_guard lk(GContext->mutex); + if (mvToolManager::GetFontManager().isInvalid()) + { + mvToolManager::GetFontManager().rebuildAtlas(); + ImGui_ImplMetal_DestroyFontsTexture(); + mvToolManager::GetFontManager().updateAtlas(); + ImGui_ImplMetal_CreateFontsTexture(graphicsData->device); + } } NSWindow *nswin = glfwGetCocoaWindow(viewportData->handle); @@ -244,15 +248,27 @@ [renderEncoder pushDebugGroup:@"ImGui demo"]; + { + // Locking the mutex while we're touching thread-sensitive data + std::lock_guard lk(GContext->mutex); + + viewport->width = (unsigned)width; + viewport->height = (unsigned)height; - // Start the Dear ImGui frame - ImGui_ImplMetal_NewFrame(graphicsData->renderPassDescriptor); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); + // Start the Dear ImGui frame + ImGui_ImplMetal_NewFrame(graphicsData->renderPassDescriptor); + ImGui_ImplGlfw_NewFrame(); - Render(); + // 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(); - glfwGetWindowPos(viewportData->handle, &viewport->xpos, &viewport->ypos); + Render(); + + glfwGetWindowPos(viewportData->handle, &viewport->xpos, &viewport->ypos); + } // Rendering ImGui::Render(); diff --git a/src/mvViewport_linux.cpp b/src/mvViewport_linux.cpp index 31f7b7f87..84e1db5d2 100644 --- a/src/mvViewport_linux.cpp +++ b/src/mvViewport_linux.cpp @@ -41,6 +41,8 @@ window_size_callback(GLFWwindow* window, int width, int height) static void mvPrerender() { + std::lock_guard lk(GContext->mutex); + mvViewport* viewport = GContext->viewport; auto viewportData = (mvViewportData*)viewport->platformSpecifics; diff --git a/src/mvViewport_win32.cpp b/src/mvViewport_win32.cpp index a8e13a00e..146b6c6ff 100644 --- a/src/mvViewport_win32.cpp +++ b/src/mvViewport_win32.cpp @@ -84,7 +84,7 @@ static void StartNewFrame() { // Font manager is thread-unsafe, so we'd better sync it - std::lock_guard lk(GContext->mutex); + std::lock_guard lk(GContext->mutex); if (mvToolManager::GetFontManager().isInvalid()) { @@ -115,7 +115,7 @@ mvPrerender(mvViewport& viewport) // An extra scope for mutex lock { // TODO: we probably need a separate mutex for this - std::lock_guard lk(GContext->mutex); + std::lock_guard lk(GContext->mutex); ApplyViewportParms(viewport); } @@ -176,7 +176,7 @@ mvHandleMsg(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept case WM_MOVE: { - std::lock_guard lk(GContext->mutex); + std::lock_guard lk(GContext->mutex); // We explicitly ignore all WM_MOVE messages until the rendering loop // starts. This is because on Windows 10 and later, the coordinates passed @@ -217,7 +217,7 @@ mvHandleMsg(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept cheight = crect.bottom - crect.top; } - std::lock_guard lk(GContext->mutex); + std::lock_guard lk(GContext->mutex); viewport->actualWidth = awidth; viewport->actualHeight = aheight; @@ -257,7 +257,7 @@ mvHandleMsg(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) noexcept if (wParam == resizeTimerID) { // TODO: we probably need a separate mutex for ApplyViewportParms - std::lock_guard lk(GContext->mutex); + std::lock_guard lk(GContext->mutex); ApplyViewportParms(*viewport); StartNewFrame(); Render(); From e24a8f2e54d7321d2bc123cd25b5b4e72d3430a3 Mon Sep 17 00:00:00 2001 From: Vladimir Ein Date: Wed, 17 Dec 2025 12:17:48 +0500 Subject: [PATCH 28/58] fix: Fixed macOS build --- src/mvViewport_apple.mm | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/mvViewport_apple.mm b/src/mvViewport_apple.mm index 8d6011c05..c2f06e745 100644 --- a/src/mvViewport_apple.mm +++ b/src/mvViewport_apple.mm @@ -252,9 +252,6 @@ // Locking the mutex while we're touching thread-sensitive data std::lock_guard lk(GContext->mutex); - viewport->width = (unsigned)width; - viewport->height = (unsigned)height; - // Start the Dear ImGui frame ImGui_ImplMetal_NewFrame(graphicsData->renderPassDescriptor); ImGui_ImplGlfw_NewFrame(); From 2362735f2215b1808b6feba0085ca9c563e82aea Mon Sep 17 00:00:00 2001 From: Vladimir Ein Date: Fri, 19 Dec 2025 09:56:28 +0500 Subject: [PATCH 29/58] fix: A better version of split_frame, releases immediately after frame rendering has ended. --- dearpygui/_dearpygui.pyi | 2 +- dearpygui/_dearpygui_RTD.py | 6 +++--- dearpygui/dearpygui.py | 12 +++++++++--- src/dearpygui_commands.h | 29 ++++++++++++++++++----------- src/dearpygui_parsers.h | 2 +- src/mvContext.cpp | 37 ++++++++++++++++++++++++++----------- src/mvContext.h | 4 +++- 7 files changed, 61 insertions(+), 31 deletions(-) diff --git a/dearpygui/_dearpygui.pyi b/dearpygui/_dearpygui.pyi index 3768e3f25..ff1f1071b 100644 --- a/dearpygui/_dearpygui.pyi +++ b/dearpygui/_dearpygui.pyi @@ -1211,7 +1211,7 @@ def show_viewport(*, minimized: bool ='', maximized: bool ='') -> None: """Shows the main viewport.""" ... -def split_frame(*, delay: int ='') -> None: +def split_frame() -> None: """Waits one frame.""" ... diff --git a/dearpygui/_dearpygui_RTD.py b/dearpygui/_dearpygui_RTD.py index 65f4f23e9..ae46b3390 100644 --- a/dearpygui/_dearpygui_RTD.py +++ b/dearpygui/_dearpygui_RTD.py @@ -8688,16 +8688,16 @@ def show_viewport(**kwargs): return internal_dpg.show_viewport(**kwargs) -def split_frame(**kwargs): +def split_frame(): """ Waits one frame. Args: - delay (int, optional): Minimal delay in in milliseconds + delay (int, optional): (deprecated)Do not use it anymore, it has no effect. Returns: None """ - return internal_dpg.split_frame(**kwargs) + return internal_dpg.split_frame() def stop_dearpygui(): """ Stops Dear PyGui diff --git a/dearpygui/dearpygui.py b/dearpygui/dearpygui.py index 6c69a5027..ed2e451ab 100644 --- a/dearpygui/dearpygui.py +++ b/dearpygui/dearpygui.py @@ -9661,16 +9661,22 @@ def show_viewport(*, minimized: bool =False, maximized: bool =False, **kwargs) - return internal_dpg.show_viewport(minimized=minimized, maximized=maximized, **kwargs) -def split_frame(*, delay: int =32, **kwargs) -> None: +def split_frame(**kwargs) -> None: """ Waits one frame. Args: - delay (int, optional): Minimal delay in in milliseconds + delay (int, optional): (deprecated) Do not use it anymore, it has no effect. Returns: None """ - return internal_dpg.split_frame(delay=delay, **kwargs) + if 'delay' in kwargs.keys(): + + warnings.warn('delay keyword removed', DeprecationWarning, 2) + + kwargs.pop('delay', None) + + return internal_dpg.split_frame(**kwargs) def stop_dearpygui(**kwargs) -> None: """ Stops Dear PyGui diff --git a/src/dearpygui_commands.h b/src/dearpygui_commands.h index cc81f4bf7..86bb62e94 100644 --- a/src/dearpygui_commands.h +++ b/src/dearpygui_commands.h @@ -2271,19 +2271,26 @@ save_init_file(PyObject* self, PyObject* args, PyObject* kwargs) static PyObject* split_frame(PyObject* self, PyObject* args, PyObject* kwargs) { - i32 delay = 32; - - if (!Parse((GetParsers())["split_frame"], args, kwargs, __FUNCTION__, - &delay)) + if (!Parse((GetParsers())["split_frame"], args, kwargs, __FUNCTION__)) return GetPyNone(); - // std::lock_guard lk(GContext->mutex); + if (GContext->running) + { + Py_BEGIN_ALLOW_THREADS; + std::unique_lock lk(GContext->frameEndedMutex); + GContext->frameEnded = false; + GContext->frameEndedEvent.wait(lk, []{return GContext->frameEnded;}); + lk.unlock(); - Py_BEGIN_ALLOW_THREADS; - GContext->waitOneFrame = true; - while (GContext->waitOneFrame) - std::this_thread::sleep_for(std::chrono::milliseconds(delay)); - Py_END_ALLOW_THREADS; + Py_END_ALLOW_THREADS; + } + + // Now let's see if it was successful (there's a chance that DPG got stopped while we were waiting) + if (!GContext->running) + { + mvThrowPythonError(mvErrorCode::mvNone, "split_frame is exiting: there is no active rendering loop."); + return nullptr; + } return GetPyNone(); } @@ -2654,7 +2661,7 @@ destroy_context(PyObject* self, PyObject* args, PyObject* kwargs) else { // Make sure everyone knows we're shutting down, even if stop_dearpygui - // was not called. + // was not called. This also releases any waiting split_frame calls. StopRendering(); Py_BEGIN_ALLOW_THREADS; diff --git a/src/dearpygui_parsers.h b/src/dearpygui_parsers.h index ef229ae01..0d6d4d745 100644 --- a/src/dearpygui_parsers.h +++ b/src/dearpygui_parsers.h @@ -564,7 +564,7 @@ InsertParser_Block1(std::map& parsers) { std::vector args; - args.push_back({ mvPyDataType::Integer, "delay", mvArgType::KEYWORD_ARG, "32", "Minimal delay in in milliseconds" }); + args.push_back({ mvPyDataType::Integer, "delay", mvArgType::DEPRECATED_REMOVE_KEYWORD_ARG, "32", "Do not use it anymore, it has no effect." }); mvPythonParserSetup setup; setup.about = "Waits one frame."; diff --git a/src/mvContext.cpp b/src/mvContext.cpp index cad61d205..cc41d22bc 100644 --- a/src/mvContext.cpp +++ b/src/mvContext.cpp @@ -182,20 +182,22 @@ Render() mvToolManager::Draw(); + if (GContext->resetTheme) { - if (GContext->resetTheme) - { - SetDefaultTheme(); - GContext->resetTheme = false; - } - - mvRunTasks(); - RenderItemRegistry(*GContext->itemRegistry); - mvRunTasks(); + SetDefaultTheme(); + GContext->resetTheme = false; } - if (GContext->waitOneFrame == true) - GContext->waitOneFrame = false; + mvRunTasks(); + RenderItemRegistry(*GContext->itemRegistry); + mvRunTasks(); + + // release split_frame if it's waiting for the frame end + { + std::lock_guard lk(GContext->frameEndedMutex); + GContext->frameEnded = true; + } + GContext->frameEndedEvent.notify_all(); } std::map& @@ -206,7 +208,20 @@ GetParsers() void StopRendering() { + // While it may seem reasonable to set it to false with frameEndedMutex locked + // (to set it simultaneously with frameEnded), we don't want to spur another + // race condition between split_frame() and is_dearpygui_running() in the + // rendering loop. Let's trigger it as early as possible. GContext->running = false; + + // Unblock handlers (or other code) that might be waiting in `split_frame()` + { + std::lock_guard lk(GContext->frameEndedMutex); + // Simulate an end of frame even though there's no real frame this time. + // Otherwise split_frame will continue waiting. + GContext->frameEnded = true; + } + GContext->frameEndedEvent.notify_all(); } void diff --git a/src/mvContext.h b/src/mvContext.h index bed6fd2da..cb2bc864e 100644 --- a/src/mvContext.h +++ b/src/mvContext.h @@ -102,7 +102,9 @@ struct mvIO struct mvContext { - std::atomic_bool waitOneFrame = false; + std::mutex frameEndedMutex; + std::condition_variable frameEndedEvent; + bool frameEnded = 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; From cc23d008299336b37f9f7c0e9d543a18655f7f20 Mon Sep 17 00:00:00 2001 From: Vladimir Ein Date: Fri, 19 Dec 2025 18:38:43 +0500 Subject: [PATCH 30/58] feat (handlers): Focus and hover handlers can now track state changes like "mouse-in" or "lost focus". --- dearpygui/_dearpygui.pyi | 8 +++++-- dearpygui/_dearpygui_RTD.py | 6 +++++ dearpygui/dearpygui.py | 14 ++++++++---- src/dearpygui.cpp | 5 +++++ src/mvAppItem.cpp | 4 ++++ src/mvAppItemState.cpp | 4 ++++ src/mvAppItemState.h | 2 ++ src/mvBasicWidgets.cpp | 22 ++---------------- src/mvContainers.cpp | 15 +++++++++++++ src/mvItemHandlers.cpp | 45 +++++++++++++++++++++++++++++-------- src/mvItemHandlers.h | 43 ++++++++++++++++++++++++++++++----- src/mvNodes.cpp | 4 ++++ src/mvTables.cpp | 1 + 13 files changed, 132 insertions(+), 41 deletions(-) diff --git a/dearpygui/_dearpygui.pyi b/dearpygui/_dearpygui.pyi index ff1f1071b..e0d70d2f1 100644 --- a/dearpygui/_dearpygui.pyi +++ b/dearpygui/_dearpygui.pyi @@ -327,7 +327,7 @@ def add_item_edited_handler(*, label: str ='', user_data: Any ='', use_internal_ """Adds an edited handler.""" ... -def add_item_focus_handler(*, label: str ='', user_data: Any ='', use_internal_label: bool ='', tag: Union[int, str] ='', parent: Union[int, str] ='', callback: Callable ='', show: bool ='') -> Union[int, str]: +def add_item_focus_handler(*, label: str ='', user_data: Any ='', use_internal_label: bool ='', tag: Union[int, str] ='', parent: Union[int, str] ='', callback: Callable ='', show: bool ='', event_type: int ='') -> Union[int, str]: """Adds a focus handler.""" ... @@ -335,7 +335,7 @@ def add_item_handler_registry(*, label: str ='', user_data: Any ='', use_interna """Adds an item handler registry.""" ... -def add_item_hover_handler(*, label: str ='', user_data: Any ='', use_internal_label: bool ='', tag: Union[int, str] ='', parent: Union[int, str] ='', callback: Callable ='', show: bool ='') -> Union[int, str]: +def add_item_hover_handler(*, label: str ='', user_data: Any ='', use_internal_label: bool ='', tag: Union[int, str] ='', parent: Union[int, str] ='', callback: Callable ='', show: bool ='', event_type: int ='') -> Union[int, str]: """Adds a hover handler.""" ... @@ -1430,6 +1430,10 @@ mvComboHeight_Small=0 mvComboHeight_Regular=0 mvComboHeight_Large=0 mvComboHeight_Largest=0 +mvEventType_Off=0 +mvEventType_Enter=0 +mvEventType_On=0 +mvEventType_Leave=0 mvPlatform_Windows=0 mvPlatform_Apple=0 mvPlatform_Linux=0 diff --git a/dearpygui/_dearpygui_RTD.py b/dearpygui/_dearpygui_RTD.py index ae46b3390..5130a5050 100644 --- a/dearpygui/_dearpygui_RTD.py +++ b/dearpygui/_dearpygui_RTD.py @@ -4940,6 +4940,7 @@ def add_item_focus_handler(**kwargs): parent (Union[int, str], optional): Parent to add this item to. (runtime adding) callback (Callable, optional): Registers a callback. show (bool, optional): Attempt to render widget. + event_type (int, optional): What kind of events to track: just got focus (mvEventType_Enter), currently having focus (mvEventType_On), lost focus (mvEventType_Leave). Can be a combination of these flags. Defaults to mvEventType_On. id (Union[int, str], optional): (deprecated) Returns: Union[int, str] @@ -4974,6 +4975,7 @@ def add_item_hover_handler(**kwargs): parent (Union[int, str], optional): Parent to add this item to. (runtime adding) callback (Callable, optional): Registers a callback. show (bool, optional): Attempt to render widget. + event_type (int, optional): What kind of events to track: mouse-in (mvEventType_Enter), mouse-over (mvEventType_On), mouse-out (mvEventType_Leave). Can be a combination of these flags. Defaults to mouse-over. id (Union[int, str], optional): (deprecated) Returns: Union[int, str] @@ -8983,6 +8985,10 @@ def unstage(item): mvComboHeight_Regular=internal_dpg.mvComboHeight_Regular mvComboHeight_Large=internal_dpg.mvComboHeight_Large mvComboHeight_Largest=internal_dpg.mvComboHeight_Largest +mvEventType_Off=internal_dpg.mvEventType_Off +mvEventType_Enter=internal_dpg.mvEventType_Enter +mvEventType_On=internal_dpg.mvEventType_On +mvEventType_Leave=internal_dpg.mvEventType_Leave mvPlatform_Windows=internal_dpg.mvPlatform_Windows mvPlatform_Apple=internal_dpg.mvPlatform_Apple mvPlatform_Linux=internal_dpg.mvPlatform_Linux diff --git a/dearpygui/dearpygui.py b/dearpygui/dearpygui.py index ed2e451ab..6d0db5a34 100644 --- a/dearpygui/dearpygui.py +++ b/dearpygui/dearpygui.py @@ -5464,7 +5464,7 @@ def add_item_edited_handler(*, label: str =None, user_data: Any =None, use_inter return internal_dpg.add_item_edited_handler(label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, parent=parent, callback=callback, show=show, **kwargs) -def add_item_focus_handler(*, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, parent: Union[int, str] =0, callback: Callable =None, show: bool =True, **kwargs) -> Union[int, str]: +def add_item_focus_handler(*, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, parent: Union[int, str] =0, callback: Callable =None, show: bool =True, event_type: int =None, **kwargs) -> Union[int, str]: """ Adds a focus handler. Args: @@ -5475,6 +5475,7 @@ def add_item_focus_handler(*, label: str =None, user_data: Any =None, use_intern parent (Union[int, str], optional): Parent to add this item to. (runtime adding) callback (Callable, optional): Registers a callback. show (bool, optional): Attempt to render widget. + event_type (int, optional): What kind of events to track: just got focus (mvEventType_Enter), currently having focus (mvEventType_On), lost focus (mvEventType_Leave). Can be a combination of these flags. Defaults to mvEventType_On. id (Union[int, str], optional): (deprecated) Returns: Union[int, str] @@ -5484,7 +5485,7 @@ def add_item_focus_handler(*, label: str =None, user_data: Any =None, use_intern warnings.warn('id keyword renamed to tag', DeprecationWarning, 2) tag=kwargs['id'] - return internal_dpg.add_item_focus_handler(label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, parent=parent, callback=callback, show=show, **kwargs) + return internal_dpg.add_item_focus_handler(label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, parent=parent, callback=callback, show=show, event_type=event_type, **kwargs) def add_item_handler_registry(*, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, show: bool =True, **kwargs) -> Union[int, str]: """ Adds an item handler registry. @@ -5506,7 +5507,7 @@ def add_item_handler_registry(*, label: str =None, user_data: Any =None, use_int return internal_dpg.add_item_handler_registry(label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, show=show, **kwargs) -def add_item_hover_handler(*, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, parent: Union[int, str] =0, callback: Callable =None, show: bool =True, **kwargs) -> Union[int, str]: +def add_item_hover_handler(*, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, parent: Union[int, str] =0, callback: Callable =None, show: bool =True, event_type: int =None, **kwargs) -> Union[int, str]: """ Adds a hover handler. Args: @@ -5517,6 +5518,7 @@ def add_item_hover_handler(*, label: str =None, user_data: Any =None, use_intern parent (Union[int, str], optional): Parent to add this item to. (runtime adding) callback (Callable, optional): Registers a callback. show (bool, optional): Attempt to render widget. + event_type (int, optional): What kind of events to track: mouse-in (mvEventType_Enter), mouse-over (mvEventType_On), mouse-out (mvEventType_Leave). Can be a combination of these flags. Defaults to mouse-over. id (Union[int, str], optional): (deprecated) Returns: Union[int, str] @@ -5526,7 +5528,7 @@ def add_item_hover_handler(*, label: str =None, user_data: Any =None, use_intern warnings.warn('id keyword renamed to tag', DeprecationWarning, 2) tag=kwargs['id'] - return internal_dpg.add_item_hover_handler(label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, parent=parent, callback=callback, show=show, **kwargs) + return internal_dpg.add_item_hover_handler(label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, parent=parent, callback=callback, show=show, event_type=event_type, **kwargs) def add_item_resize_handler(*, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, parent: Union[int, str] =0, callback: Callable =None, show: bool =True, **kwargs) -> Union[int, str]: """ Adds a resize handler. @@ -9962,6 +9964,10 @@ def unstage(item : Union[int, str], **kwargs) -> None: mvComboHeight_Regular=internal_dpg.mvComboHeight_Regular mvComboHeight_Large=internal_dpg.mvComboHeight_Large mvComboHeight_Largest=internal_dpg.mvComboHeight_Largest +mvEventType_Off=internal_dpg.mvEventType_Off +mvEventType_Enter=internal_dpg.mvEventType_Enter +mvEventType_On=internal_dpg.mvEventType_On +mvEventType_Leave=internal_dpg.mvEventType_Leave mvPlatform_Windows=internal_dpg.mvPlatform_Windows mvPlatform_Apple=internal_dpg.mvPlatform_Apple mvPlatform_Linux=internal_dpg.mvPlatform_Linux diff --git a/src/dearpygui.cpp b/src/dearpygui.cpp index 867d382cb..27145a651 100644 --- a/src/dearpygui.cpp +++ b/src/dearpygui.cpp @@ -69,6 +69,11 @@ GetModuleConstants() ModuleConstants.push_back({"mvComboHeight_Large", 2L }); ModuleConstants.push_back({"mvComboHeight_Largest", 3L }); + ModuleConstants.push_back({"mvEventType_Off", mvEventType_Off }); + ModuleConstants.push_back({"mvEventType_Enter", mvEventType_Enter }); + ModuleConstants.push_back({"mvEventType_On", mvEventType_On }); + ModuleConstants.push_back({"mvEventType_Leave", mvEventType_Leave }); + ModuleConstants.push_back({"mvPlatform_Windows", 0L }); ModuleConstants.push_back({"mvPlatform_Apple", 1L }); ModuleConstants.push_back({"mvPlatform_Linux", 2L }); diff --git a/src/mvAppItem.cpp b/src/mvAppItem.cpp index b5fdffbf6..bae62efd6 100644 --- a/src/mvAppItem.cpp +++ b/src/mvAppItem.cpp @@ -4855,6 +4855,8 @@ DearPyGui::GetEntityParser(mvAppItemType type) MV_PARSER_ARG_CALLBACK) ); + args.push_back({ mvPyDataType::Integer, "event_type", mvArgType::KEYWORD_ARG, "None", "What kind of events to track: mouse-in (mvEventType_Enter), mouse-over (mvEventType_On), mouse-out (mvEventType_Leave). Can be a combination of these flags. Defaults to mouse-over." }); + setup.about = "Adds a hover handler."; setup.category = { "Widgets", "Events" }; break; @@ -4881,6 +4883,8 @@ DearPyGui::GetEntityParser(mvAppItemType type) MV_PARSER_ARG_CALLBACK) ); + args.push_back({ mvPyDataType::Integer, "event_type", mvArgType::KEYWORD_ARG, "None", "What kind of events to track: just got focus (mvEventType_Enter), currently having focus (mvEventType_On), lost focus (mvEventType_Leave). Can be a combination of these flags. Defaults to mvEventType_On." }); + setup.about = "Adds a focus handler."; setup.category = { "Widgets", "Events" }; break; diff --git a/src/mvAppItemState.cpp b/src/mvAppItemState.cpp index da166671b..33a0d5b0b 100644 --- a/src/mvAppItemState.cpp +++ b/src/mvAppItemState.cpp @@ -8,8 +8,10 @@ void ResetAppItemState(mvAppItemState& state) { state.hovered = false; + state.prevHovered = false; state.active = false; state.focused = false; + state.prevFocused = false; state.leftclicked = false; state.rightclicked = false; state.middleclicked = false; @@ -27,8 +29,10 @@ void UpdateAppItemState(mvAppItemState& state) { state.lastFrameUpdate = GContext->frame; + state.prevHovered = state.hovered; state.hovered = ImGui::IsItemHovered(); state.active = ImGui::IsItemActive(); + state.prevFocused = state.focused; state.focused = ImGui::IsItemFocused(); if (state.focused) { diff --git a/src/mvAppItemState.h b/src/mvAppItemState.h index b635645a7..691326387 100644 --- a/src/mvAppItemState.h +++ b/src/mvAppItemState.h @@ -66,8 +66,10 @@ inline b8 IsItemDoubleClicked(ImGuiMouseButton mouse_button) struct mvAppItemState { b8 hovered = false; + b8 prevHovered = false; b8 active = false; b8 focused = false; + b8 prevFocused = false; b8 leftclicked = false; b8 rightclicked = false; b8 middleclicked = false; diff --git a/src/mvBasicWidgets.cpp b/src/mvBasicWidgets.cpp index 7c009d96e..b7b66aae3 100644 --- a/src/mvBasicWidgets.cpp +++ b/src/mvBasicWidgets.cpp @@ -2642,6 +2642,7 @@ DearPyGui::draw_simple_plot(ImDrawList* drawlist, mvAppItem& item, const mvSimpl // * only update if applicable //----------------------------------------------------------------------------- item.state.lastFrameUpdate = GContext->frame; + item.state.prevHovered = item.state.hovered; item.state.hovered = ImGui::IsItemHovered(); item.state.leftclicked = ImGui::IsItemClicked(); item.state.rightclicked = ImGui::IsItemClicked(1); @@ -4388,26 +4389,7 @@ DearPyGui::draw_radio_button(ImDrawList* drawlist, mvAppItem& item, mvRadioButto //----------------------------------------------------------------------------- // update state //----------------------------------------------------------------------------- - item.state.lastFrameUpdate = GContext->frame; - item.state.hovered = ImGui::IsItemHovered(); - item.state.active = ImGui::IsItemActive(); - item.state.focused = ImGui::IsItemFocused(); - item.state.leftclicked = ImGui::IsItemClicked(); - item.state.rightclicked = ImGui::IsItemClicked(1); - item.state.middleclicked = ImGui::IsItemClicked(2); - for (int i = 0; i < item.state.doubleclicked.size(); i++) - { - item.state.doubleclicked[i] = IsItemDoubleClicked(i); - } - item.state.visible = ImGui::IsItemVisible(); - item.state.activated = ImGui::IsItemActivated(); - item.state.deactivated = ImGui::IsItemDeactivated(); - item.state.deactivatedAfterEdit = ImGui::IsItemDeactivatedAfterEdit(); - item.state.toggledOpen = ImGui::IsItemToggledOpen(); - item.state.rectMin = { ImGui::GetItemRectMin().x, ImGui::GetItemRectMin().y }; - item.state.rectMax = { ImGui::GetItemRectMax().x, ImGui::GetItemRectMax().y }; - item.state.rectSize = { ImGui::GetItemRectSize().x, ImGui::GetItemRectSize().y }; - item.state.contextRegionAvail = { ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y }; + UpdateAppItemState(item.state); //----------------------------------------------------------------------------- // post draw diff --git a/src/mvContainers.cpp b/src/mvContainers.cpp index 93969a59a..9b097909b 100644 --- a/src/mvContainers.cpp +++ b/src/mvContainers.cpp @@ -639,7 +639,9 @@ DearPyGui::draw_menu(ImDrawList* drawlist, mvAppItem& item, mvMenuConfig& config item.state.active = ImGui::IsItemActive(); item.state.activated = ImGui::IsItemActivated(); item.state.deactivated = ImGui::IsItemDeactivated(); + item.state.prevFocused = item.state.focused; item.state.focused = ImGui::IsWindowFocused(); + item.state.prevHovered = item.state.hovered; item.state.hovered = ImGui::IsWindowHovered(); item.state.rectSize = { ImGui::GetWindowWidth(), ImGui::GetWindowHeight() }; item.state.contextRegionAvail = { ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y }; @@ -682,7 +684,9 @@ DearPyGui::draw_menu(ImDrawList* drawlist, mvAppItem& item, mvMenuConfig& config item.state.active = ImGui::IsItemActive(); item.state.activated = ImGui::IsItemActivated(); item.state.deactivated = ImGui::IsItemDeactivated(); + item.state.prevFocused = item.state.focused; item.state.focused = false; + item.state.prevHovered = item.state.hovered; item.state.hovered = false; item.state.rectSize = { 0.0f, 0.0f }; item.state.contextRegionAvail = { ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y }; @@ -779,6 +783,7 @@ DearPyGui::draw_tab(ImDrawList* drawlist, mvAppItem& item, mvTabConfig& config) } item.state.lastFrameUpdate = GContext->frame; + item.state.prevHovered = item.state.hovered; // create tab item and see if it is selected if (ImGui::BeginTabItem(item.info.internalLabel.c_str(), config.closable ? &item.config.show : nullptr, config._flags)) { @@ -927,7 +932,9 @@ DearPyGui::draw_child_window(ImDrawList* drawlist, mvAppItem& item, mvChildWindo item.state.lastFrameUpdate = GContext->frame; item.state.active = ImGui::IsItemActive(); item.state.deactivated = ImGui::IsItemDeactivated(); + item.state.prevFocused = item.state.focused; item.state.focused = ImGui::IsWindowFocused(); + item.state.prevHovered = item.state.hovered; item.state.hovered = ImGui::IsWindowHovered(); item.state.rectSize = { ImGui::GetWindowWidth(), ImGui::GetWindowHeight() }; item.state.contextRegionAvail = { ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y }; @@ -1511,7 +1518,9 @@ DearPyGui::draw_window(ImDrawList* drawlist, mvAppItem& item, mvWindowAppItemCon // shouldn't have to do this but do. Fix later item.config.show = false; item.state.lastFrameUpdate = GContext->frame; + item.state.prevHovered = item.state.hovered; item.state.hovered = false; + item.state.prevFocused = item.state.focused; item.state.focused = false; item.state.toggledOpen = false; item.state.visible = false; @@ -1550,7 +1559,9 @@ DearPyGui::draw_window(ImDrawList* drawlist, mvAppItem& item, mvWindowAppItemCon item.config.show = false; // Update item state so that get_item_state is valid item.state.lastFrameUpdate = GContext->frame; + item.state.prevHovered = item.state.hovered; item.state.hovered = false; + item.state.prevFocused = item.state.focused; item.state.focused = false; item.state.toggledOpen = false; item.state.visible = false; @@ -1658,7 +1669,9 @@ DearPyGui::draw_window(ImDrawList* drawlist, mvAppItem& item, mvWindowAppItemCon item.state.lastFrameUpdate = GContext->frame; item.state.visible = true; + item.state.prevHovered = item.state.hovered; item.state.hovered = ImGui::IsWindowHovered(); + item.state.prevFocused = item.state.focused; item.state.focused = ImGui::IsWindowFocused(); item.state.rectSize = { ImGui::GetWindowSize().x, ImGui::GetWindowSize().y }; item.state.toggledOpen = ImGui::IsWindowCollapsed(); @@ -1705,7 +1718,9 @@ DearPyGui::draw_window(ImDrawList* drawlist, mvAppItem& item, mvWindowAppItemCon if (!item.config.show) { item.state.lastFrameUpdate = GContext->frame; + item.state.prevHovered = item.state.hovered; item.state.hovered = false; + item.state.prevFocused = item.state.focused; item.state.focused = false; item.state.toggledOpen = false; item.state.visible = false; diff --git a/src/mvItemHandlers.cpp b/src/mvItemHandlers.cpp index 01dca1844..f8491a475 100644 --- a/src/mvItemHandlers.cpp +++ b/src/mvItemHandlers.cpp @@ -131,6 +131,40 @@ void mvItemHandler::submitHandler(mvAppItem* parent) }); } +void mvBoolStateHandler::checkEvent(bool curState, bool prevState, mvAppItem* parent) +{ + mvEventType eventType = curState? + (prevState? mvEventType_On : mvEventType_EnterAndOn) : + (prevState? mvEventType_LeaveAndOff : mvEventType_Off); + + if (trackedEventType & eventType) + { + // We do not pass eventType to callback yet in order to keep it compatible + // with the old version. + submitHandler(parent); + } +} + +void mvBoolStateHandler::handleSpecificKeywordArgs(PyObject* dict) +{ + if (dict == nullptr) + return; + + if (PyObject* item = PyDict_GetItemString(dict, "event_type")) + { + if (item != Py_None) + trackedEventType = static_cast(ToInt(item)); + } +} + +void mvBoolStateHandler::getSpecificConfiguration(PyObject* dict) +{ + if (dict == nullptr) + return; + + PyDict_SetItemString(dict, "event_type", mvPyObject(ToPyInt(trackedEventType))); +} + void mvActivatedHandler::customAction(void* data) { @@ -277,21 +311,14 @@ void mvEditedHandler::customAction(void* data) void mvFocusHandler::customAction(void* data) { - mvAppItemState* state = static_cast(data); - if (state->focused) - { - submitHandler(state->parent); - } + checkEvent(state->focused, state->prevFocused, state->parent); } void mvHoverHandler::customAction(void* data) { mvAppItemState* state = static_cast(data); - if (state->hovered) - { - submitHandler(state->parent); - } + checkEvent(state->hovered, state->prevHovered, state->parent); } void mvResizeHandler::customAction(void* data) diff --git a/src/mvItemHandlers.h b/src/mvItemHandlers.h index f49aa92eb..d32a91d06 100644 --- a/src/mvItemHandlers.h +++ b/src/mvItemHandlers.h @@ -23,6 +23,39 @@ class mvItemHandler : public mvAppItem void submitHandler(mvAppItem* parent); }; +enum mvEventType +{ + // These constants can be used as a combination of flags + mvEventType_None = 0, + mvEventType_Off = 1 << 1, + mvEventType_Enter = 1 << 2, + mvEventType_On = 1 << 3, + mvEventType_Leave = 1 << 4, + // When the state changes, we observe two events in a single frame: + // both "enter" and "on" or both "leave" and "off". We need to define + // these flags here so that they can be used as a mask later on. + mvEventType_EnterAndOn = mvEventType_Enter | mvEventType_On, + mvEventType_LeaveAndOff = mvEventType_Leave | mvEventType_Off, + // This is the state the handler will react to by default + mvEventType_Default = mvEventType_On +}; + +// This is a base class for handlers that monitor a single bool variable +// that can switch on or off and reflects the item state, like "focused" +// or "hovered". +class mvBoolStateHandler : public mvItemHandler +{ +public: + explicit mvBoolStateHandler(mvUUID uuid) : mvItemHandler(uuid) {} + void draw(ImDrawList* drawlist, float x, float y) override {} + void checkEvent(bool curState, bool prevState, mvAppItem* parent); + void handleSpecificKeywordArgs(PyObject* dict) override; + void getSpecificConfiguration(PyObject* dict) override; + +protected: + mvEventType trackedEventType = mvEventType_Default; +}; + class mvActivatedHandler : public mvItemHandler { public: @@ -89,19 +122,17 @@ class mvEditedHandler : public mvItemHandler void customAction(void* data = nullptr) override; }; -class mvFocusHandler : public mvItemHandler +class mvFocusHandler : public mvBoolStateHandler { public: - explicit mvFocusHandler(mvUUID uuid) : mvItemHandler(uuid) {} - void draw(ImDrawList* drawlist, float x, float y) override {} + explicit mvFocusHandler(mvUUID uuid) : mvBoolStateHandler(uuid) {} void customAction(void* data = nullptr) override; }; -class mvHoverHandler : public mvItemHandler +class mvHoverHandler : public mvBoolStateHandler { public: - explicit mvHoverHandler(mvUUID uuid) : mvItemHandler(uuid) {} - void draw(ImDrawList* drawlist, float x, float y) override {} + explicit mvHoverHandler(mvUUID uuid) : mvBoolStateHandler(uuid) {} void customAction(void* data = nullptr) override; }; diff --git a/src/mvNodes.cpp b/src/mvNodes.cpp index 71635ddfd..6f81f1bd2 100644 --- a/src/mvNodes.cpp +++ b/src/mvNodes.cpp @@ -213,6 +213,7 @@ void mvNodeEditor::draw(ImDrawList* drawlist, float x, float y) } state.lastFrameUpdate = GContext->frame; + state.prevHovered = state.hovered; state.hovered = ImNodes::IsEditorHovered(); state.visible = ret; state.rectSize = { ImNodes::mvEditorGetSize().Max.x - ImNodes::mvEditorGetSize().Min.x, ImNodes::mvEditorGetSize().Max.y - ImNodes::mvEditorGetSize().Min.y }; @@ -240,6 +241,7 @@ void mvNodeEditor::draw(ImDrawList* drawlist, float x, float y) for (auto& child : childslots[0]) { child->state.lastFrameUpdate = GContext->frame; + child->state.prevHovered = child->state.hovered; child->state.hovered = false; if (anyLinkHovered && linkHovered == static_cast(child.get())->getId()) @@ -250,6 +252,7 @@ void mvNodeEditor::draw(ImDrawList* drawlist, float x, float y) for (auto& child : childslots[1]) { child->state.lastFrameUpdate = GContext->frame; + child->state.prevHovered = child->state.hovered; child->state.hovered = false; if (child->config.show) @@ -267,6 +270,7 @@ void mvNodeEditor::draw(ImDrawList* drawlist, float x, float y) for (auto& grandchild : child->childslots[1]) { grandchild->state.lastFrameUpdate = GContext->frame; + grandchild->state.prevHovered = grandchild->state.hovered; grandchild->state.hovered = false; if (anyPinHovered && pinHovered == static_cast(grandchild.get())->getId()) diff --git a/src/mvTables.cpp b/src/mvTables.cpp index b02b8ec20..4bcd43809 100644 --- a/src/mvTables.cpp +++ b/src/mvTables.cpp @@ -360,6 +360,7 @@ void mvTable::draw(ImDrawList* drawlist, float x, float y) ImGuiTableColumnFlags flags = ImGui::TableGetColumnFlags(columnnum); item->state.lastFrameUpdate = GContext->frame; item->state.visible = flags & ImGuiTableColumnFlags_IsVisible; + item->state.prevHovered = item->state.hovered; item->state.hovered = flags & ImGuiTableColumnFlags_IsHovered; // Note: when the table is empty, TableGetColumnFlags will incorrectly return // zero status flags for all columns. While this is fine for `visible` and `hovered`, From 4f3c50ec5cadde6637ecfb3a61dbc03e69339877 Mon Sep 17 00:00:00 2001 From: Vladimir Ein Date: Fri, 19 Dec 2025 10:05:19 +0500 Subject: [PATCH 31/58] feat: Added mvScrollHandler --- dearpygui/_dearpygui.pyi | 20 ++++-- dearpygui/_dearpygui_RTD.py | 44 +++++++++--- dearpygui/dearpygui.py | 48 +++++++++++--- src/dearpygui.cpp | 10 +++ src/dearpygui_commands.h | 129 ++++++------------------------------ src/dearpygui_parsers.h | 18 ++++- src/mvAppItem.cpp | 63 +++++++++++++++++- src/mvAppItem.h | 20 +++++- src/mvAppItemState.cpp | 21 ++++++ src/mvAppItemState.h | 15 ++++- src/mvAppItemTypes.inc | 1 + src/mvContainers.cpp | 52 +++------------ src/mvContainers.h | 12 ---- src/mvItemHandlers.cpp | 50 ++++++++++++++ src/mvItemHandlers.h | 20 ++++++ src/mvPyUtils.cpp | 11 +++ src/mvPyUtils.h | 1 + src/mvTables.cpp | 29 +++----- src/mvTables.h | 7 -- 19 files changed, 357 insertions(+), 214 deletions(-) diff --git a/dearpygui/_dearpygui.pyi b/dearpygui/_dearpygui.pyi index e0d70d2f1..3ffbd5356 100644 --- a/dearpygui/_dearpygui.pyi +++ b/dearpygui/_dearpygui.pyi @@ -343,6 +343,10 @@ def add_item_resize_handler(*, label: str ='', user_data: Any ='', use_internal_ """Adds a resize handler.""" ... +def add_item_scroll_handler(*, label: str ='', user_data: Any ='', use_internal_label: bool ='', tag: Union[int, str] ='', parent: Union[int, str] ='', callback: Callable ='', show: bool ='') -> Union[int, str]: + """Adds a scroll handler.""" + ... + def add_item_toggled_open_handler(*, label: str ='', user_data: Any ='', use_internal_label: bool ='', tag: Union[int, str] ='', parent: Union[int, str] ='', callback: Callable ='', show: bool ='') -> Union[int, str]: """Adds a togged open handler.""" ... @@ -1179,12 +1183,12 @@ def set_viewport_resize_callback(callback : Callable, *, user_data: Any ='') -> """Sets a callback to run on viewport resize.""" ... -def set_x_scroll(item : Union[int, str], value : float) -> None: - """Undocumented""" +def set_x_scroll(item : Union[int, str], value : float, *, when: int ='') -> None: + """Sets horizontal scroll position.""" ... -def set_y_scroll(item : Union[int, str], value : float) -> None: - """Undocumented""" +def set_y_scroll(item : Union[int, str], value : float, *, when: int ='') -> None: + """Sets vertical scroll position.""" ... def setup_dearpygui() -> None: @@ -1434,6 +1438,13 @@ mvEventType_Off=0 mvEventType_Enter=0 mvEventType_On=0 mvEventType_Leave=0 +mvSetScrollFlags_Now=0 +mvSetScrollFlags_Delayed=0 +mvSetScrollFlags_Both=0 +mvScrollDirection_XAxis=0 +mvScrollDirection_YAxis=0 +mvScrollDirection_Horizontal=0 +mvScrollDirection_Vertical=0 mvPlatform_Windows=0 mvPlatform_Apple=0 mvPlatform_Linux=0 @@ -1856,6 +1867,7 @@ mvDeactivatedAfterEditHandler=0 mvToggledOpenHandler=0 mvClickedHandler=0 mvDoubleClickedHandler=0 +mvScrollHandler=0 mvDragPayload=0 mvResizeHandler=0 mvFont=0 diff --git a/dearpygui/_dearpygui_RTD.py b/dearpygui/_dearpygui_RTD.py index 5130a5050..abb68f6a9 100644 --- a/dearpygui/_dearpygui_RTD.py +++ b/dearpygui/_dearpygui_RTD.py @@ -5001,6 +5001,24 @@ def add_item_resize_handler(**kwargs): return internal_dpg.add_item_resize_handler(**kwargs) +def add_item_scroll_handler(**kwargs): + """ Adds a scroll handler. + + Args: + label (str, optional): Overrides 'name' as label. + user_data (Any, optional): User data for callbacks + use_internal_label (bool, optional): Use generated internal label instead of user specified (appends ### uuid). + tag (Union[int, str], optional): Unique id used to programmatically refer to the item.If label is unused this will be the label. + parent (Union[int, str], optional): Parent to add this item to. (runtime adding) + callback (Callable, optional): Registers a callback. + show (bool, optional): Attempt to render widget. + id (Union[int, str], optional): (deprecated) + Returns: + Union[int, str] + """ + + return internal_dpg.add_item_scroll_handler(**kwargs) + def add_item_toggled_open_handler(**kwargs): """ Adds a togged open handler. @@ -8600,29 +8618,31 @@ def set_viewport_resize_callback(callback, **kwargs): return internal_dpg.set_viewport_resize_callback(callback, **kwargs) -def set_x_scroll(item, value): - """ Undocumented +def set_x_scroll(item, value, **kwargs): + """ Sets horizontal scroll position. Args: item (Union[int, str]): - value (float): + value (float): Scroll position + when (int, optional): Specifies whether the scroll position will be set in the nearest frame (mvSetScrollFlags_Now) or with a 1-frame delay (mvSetScrollFlags_Delayed). The former prevents flickering, the latter works better if contents change in the same frame as when set_x_scroll called. mvSetScrollFlags_Both can also be used to set the position twice. Returns: None """ - return internal_dpg.set_x_scroll(item, value) + return internal_dpg.set_x_scroll(item, value, **kwargs) -def set_y_scroll(item, value): - """ Undocumented +def set_y_scroll(item, value, **kwargs): + """ Sets vertical scroll position. Args: item (Union[int, str]): - value (float): + value (float): Scroll position + when (int, optional): Specifies whether the scroll position will be set in the nearest frame (mvSetScrollFlags_Now) or with a 1-frame delay (mvSetScrollFlags_Delayed). The former prevents flickering, the latter works better if contents change in the same frame as when set_x_scroll called. mvSetScrollFlags_Both can also be used to set the position twice. Returns: None """ - return internal_dpg.set_y_scroll(item, value) + return internal_dpg.set_y_scroll(item, value, **kwargs) def setup_dearpygui(): """ Sets up Dear PyGui @@ -8989,6 +9009,13 @@ def unstage(item): mvEventType_Enter=internal_dpg.mvEventType_Enter mvEventType_On=internal_dpg.mvEventType_On mvEventType_Leave=internal_dpg.mvEventType_Leave +mvSetScrollFlags_Now=internal_dpg.mvSetScrollFlags_Now +mvSetScrollFlags_Delayed=internal_dpg.mvSetScrollFlags_Delayed +mvSetScrollFlags_Both=internal_dpg.mvSetScrollFlags_Both +mvScrollDirection_XAxis=internal_dpg.mvScrollDirection_XAxis +mvScrollDirection_YAxis=internal_dpg.mvScrollDirection_YAxis +mvScrollDirection_Horizontal=internal_dpg.mvScrollDirection_Horizontal +mvScrollDirection_Vertical=internal_dpg.mvScrollDirection_Vertical mvPlatform_Windows=internal_dpg.mvPlatform_Windows mvPlatform_Apple=internal_dpg.mvPlatform_Apple mvPlatform_Linux=internal_dpg.mvPlatform_Linux @@ -9411,6 +9438,7 @@ def unstage(item): mvToggledOpenHandler=internal_dpg.mvToggledOpenHandler mvClickedHandler=internal_dpg.mvClickedHandler mvDoubleClickedHandler=internal_dpg.mvDoubleClickedHandler +mvScrollHandler=internal_dpg.mvScrollHandler mvDragPayload=internal_dpg.mvDragPayload mvResizeHandler=internal_dpg.mvResizeHandler mvFont=internal_dpg.mvFont diff --git a/dearpygui/dearpygui.py b/dearpygui/dearpygui.py index 6d0db5a34..aa4c2d846 100644 --- a/dearpygui/dearpygui.py +++ b/dearpygui/dearpygui.py @@ -5552,6 +5552,28 @@ def add_item_resize_handler(*, label: str =None, user_data: Any =None, use_inter return internal_dpg.add_item_resize_handler(label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, parent=parent, callback=callback, show=show, **kwargs) +def add_item_scroll_handler(*, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, parent: Union[int, str] =0, callback: Callable =None, show: bool =True, **kwargs) -> Union[int, str]: + """ Adds a scroll handler. + + Args: + label (str, optional): Overrides 'name' as label. + user_data (Any, optional): User data for callbacks + use_internal_label (bool, optional): Use generated internal label instead of user specified (appends ### uuid). + tag (Union[int, str], optional): Unique id used to programmatically refer to the item.If label is unused this will be the label. + parent (Union[int, str], optional): Parent to add this item to. (runtime adding) + callback (Callable, optional): Registers a callback. + show (bool, optional): Attempt to render widget. + id (Union[int, str], optional): (deprecated) + Returns: + Union[int, str] + """ + + if 'id' in kwargs.keys(): + warnings.warn('id keyword renamed to tag', DeprecationWarning, 2) + tag=kwargs['id'] + + return internal_dpg.add_item_scroll_handler(label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, parent=parent, callback=callback, show=show, **kwargs) + def add_item_toggled_open_handler(*, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, parent: Union[int, str] =0, callback: Callable =None, show: bool =True, **kwargs) -> Union[int, str]: """ Adds a togged open handler. @@ -9561,29 +9583,31 @@ def set_viewport_resize_callback(callback : Callable, *, user_data: Any =None, * return internal_dpg.set_viewport_resize_callback(callback, user_data=user_data, **kwargs) -def set_x_scroll(item : Union[int, str], value : float, **kwargs) -> None: - """ Undocumented +def set_x_scroll(item : Union[int, str], value : float, *, when: int =internal_dpg.mvSetScrollFlags_Delayed, **kwargs) -> None: + """ Sets horizontal scroll position. Args: item (Union[int, str]): - value (float): + value (float): Scroll position + when (int, optional): Specifies whether the scroll position will be set in the nearest frame (mvSetScrollFlags_Now) or with a 1-frame delay (mvSetScrollFlags_Delayed). The former prevents flickering, the latter works better if contents change in the same frame as when set_x_scroll called. mvSetScrollFlags_Both can also be used to set the position twice. Returns: None """ - return internal_dpg.set_x_scroll(item, value, **kwargs) + return internal_dpg.set_x_scroll(item, value, when=when, **kwargs) -def set_y_scroll(item : Union[int, str], value : float, **kwargs) -> None: - """ Undocumented +def set_y_scroll(item : Union[int, str], value : float, *, when: int =internal_dpg.mvSetScrollFlags_Delayed, **kwargs) -> None: + """ Sets vertical scroll position. Args: item (Union[int, str]): - value (float): + value (float): Scroll position + when (int, optional): Specifies whether the scroll position will be set in the nearest frame (mvSetScrollFlags_Now) or with a 1-frame delay (mvSetScrollFlags_Delayed). The former prevents flickering, the latter works better if contents change in the same frame as when set_x_scroll called. mvSetScrollFlags_Both can also be used to set the position twice. Returns: None """ - return internal_dpg.set_y_scroll(item, value, **kwargs) + return internal_dpg.set_y_scroll(item, value, when=when, **kwargs) def setup_dearpygui(**kwargs) -> None: """ Sets up Dear PyGui @@ -9968,6 +9992,13 @@ def unstage(item : Union[int, str], **kwargs) -> None: mvEventType_Enter=internal_dpg.mvEventType_Enter mvEventType_On=internal_dpg.mvEventType_On mvEventType_Leave=internal_dpg.mvEventType_Leave +mvSetScrollFlags_Now=internal_dpg.mvSetScrollFlags_Now +mvSetScrollFlags_Delayed=internal_dpg.mvSetScrollFlags_Delayed +mvSetScrollFlags_Both=internal_dpg.mvSetScrollFlags_Both +mvScrollDirection_XAxis=internal_dpg.mvScrollDirection_XAxis +mvScrollDirection_YAxis=internal_dpg.mvScrollDirection_YAxis +mvScrollDirection_Horizontal=internal_dpg.mvScrollDirection_Horizontal +mvScrollDirection_Vertical=internal_dpg.mvScrollDirection_Vertical mvPlatform_Windows=internal_dpg.mvPlatform_Windows mvPlatform_Apple=internal_dpg.mvPlatform_Apple mvPlatform_Linux=internal_dpg.mvPlatform_Linux @@ -10390,6 +10421,7 @@ def unstage(item : Union[int, str], **kwargs) -> None: mvToggledOpenHandler=internal_dpg.mvToggledOpenHandler mvClickedHandler=internal_dpg.mvClickedHandler mvDoubleClickedHandler=internal_dpg.mvDoubleClickedHandler +mvScrollHandler=internal_dpg.mvScrollHandler mvDragPayload=internal_dpg.mvDragPayload mvResizeHandler=internal_dpg.mvResizeHandler mvFont=internal_dpg.mvFont diff --git a/src/dearpygui.cpp b/src/dearpygui.cpp index 27145a651..d26509634 100644 --- a/src/dearpygui.cpp +++ b/src/dearpygui.cpp @@ -74,6 +74,16 @@ GetModuleConstants() ModuleConstants.push_back({"mvEventType_On", mvEventType_On }); ModuleConstants.push_back({"mvEventType_Leave", mvEventType_Leave }); + // We're not adding 'None' because it's useless in the API + ModuleConstants.push_back({"mvSetScrollFlags_Now", mvSetScrollFlags_Now }); + ModuleConstants.push_back({"mvSetScrollFlags_Delayed", mvSetScrollFlags_Delayed }); + ModuleConstants.push_back({"mvSetScrollFlags_Both", mvSetScrollFlags_Both }); + + ModuleConstants.push_back({"mvScrollDirection_XAxis", mvScrollDirection_XAxis }); + ModuleConstants.push_back({"mvScrollDirection_YAxis", mvScrollDirection_YAxis }); + ModuleConstants.push_back({"mvScrollDirection_Horizontal", mvScrollDirection_Horizontal }); + ModuleConstants.push_back({"mvScrollDirection_Vertical", mvScrollDirection_Vertical }); + ModuleConstants.push_back({"mvPlatform_Windows", 0L }); ModuleConstants.push_back({"mvPlatform_Apple", 1L }); ModuleConstants.push_back({"mvPlatform_Linux", 2L }); diff --git a/src/dearpygui_commands.h b/src/dearpygui_commands.h index 86bb62e94..e25355bc2 100644 --- a/src/dearpygui_commands.h +++ b/src/dearpygui_commands.h @@ -207,9 +207,10 @@ set_x_scroll(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* itemraw; float value; + int when = (int)mvSetScrollFlags_Delayed; if (!Parse((GetParsers())["set_x_scroll"], args, kwargs, __FUNCTION__, - &itemraw, &value)) + &itemraw, &value, &when)) return GetPyNone(); mvPySafeLockGuard lk(GContext->mutex); @@ -224,25 +225,10 @@ set_x_scroll(PyObject* self, PyObject* args, PyObject* kwargs) return GetPyNone(); } - if (window->type == mvAppItemType::mvWindowAppItem) + if (DearPyGui::GetApplicableState(window->type) & MV_STATE_SCROLL) { - - auto pWindow = static_cast(window); - - pWindow->configData.scrollX = value; - pWindow->configData._scrollXSet = true; - } - else if (window->type == mvAppItemType::mvChildWindow) - { - auto pChild = static_cast(window); - pChild->configData.scrollX = value; - pChild->configData._scrollXSet = true; - } - else if (window->type == mvAppItemType::mvTable) - { - auto pChild = static_cast(window); - pChild->_scrollX = value; - pChild->_scrollXSet = true; + window->config.scrollX = value; + window->config.scrollXFlags = (mvSetScrollFlags)when; } else { @@ -259,9 +245,10 @@ set_y_scroll(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* itemraw; float value; + int when = (int)mvSetScrollFlags_Delayed; if (!Parse((GetParsers())["set_y_scroll"], args, kwargs, __FUNCTION__, - &itemraw, &value)) + &itemraw, &value, &when)) return GetPyNone(); mvPySafeLockGuard lk(GContext->mutex); @@ -276,25 +263,10 @@ set_y_scroll(PyObject* self, PyObject* args, PyObject* kwargs) return GetPyNone(); } - if (window->type == mvAppItemType::mvWindowAppItem) - { - - auto pWindow = static_cast(window); - - pWindow->configData.scrollY = value; - pWindow->configData._scrollYSet = true; - } - else if (window->type == mvAppItemType::mvChildWindow) - { - auto pChild = static_cast(window); - pChild->configData.scrollY = value; - pChild->configData._scrollYSet = true; - } - else if (window->type == mvAppItemType::mvTable) + if (DearPyGui::GetApplicableState(window->type) & MV_STATE_SCROLL) { - auto pChild = static_cast(window); - pChild->_scrollY = value; - pChild->_scrollYSet = true; + window->config.scrollY = value; + window->config.scrollYFlags = (mvSetScrollFlags)when; } else { @@ -327,24 +299,9 @@ get_x_scroll(PyObject* self, PyObject* args, PyObject* kwargs) return GetPyNone(); } - if (window->type == mvAppItemType::mvWindowAppItem) + if (DearPyGui::GetApplicableState(window->type) & MV_STATE_SCROLL) { - - auto pWindow = static_cast(window); - - return ToPyFloat(pWindow->configData.scrollX); - } - else if (window->type == mvAppItemType::mvChildWindow) - { - auto pChild = static_cast(window); - - return ToPyFloat(pChild->configData.scrollX); - } - else if (window->type == mvAppItemType::mvTable) - { - auto pTable = static_cast(window); - - return ToPyFloat(pTable->_scrollX); + return ToPyFloat(window->state.scrollPos.x); } else { @@ -377,24 +334,9 @@ get_y_scroll(PyObject* self, PyObject* args, PyObject* kwargs) return GetPyNone(); } - if (window->type == mvAppItemType::mvWindowAppItem) + if (DearPyGui::GetApplicableState(window->type) & MV_STATE_SCROLL) { - - auto pWindow = static_cast(window); - - return ToPyFloat(pWindow->configData.scrollY); - } - else if (window->type == mvAppItemType::mvChildWindow) - { - auto pChild = static_cast(window); - - return ToPyFloat(pChild->configData.scrollY); - } - else if (window->type == mvAppItemType::mvTable) - { - auto pTable = static_cast(window); - - return ToPyFloat(pTable->_scrollY); + return ToPyFloat(window->state.scrollPos.y); } else { @@ -427,24 +369,9 @@ get_x_scroll_max(PyObject* self, PyObject* args, PyObject* kwargs) return GetPyNone(); } - if (window->type == mvAppItemType::mvWindowAppItem) - { - - auto pWindow = static_cast(window); - - return ToPyFloat(pWindow->configData.scrollMaxX); - } - else if (window->type == mvAppItemType::mvChildWindow) - { - auto pChild = static_cast(window); - - return ToPyFloat(pChild->configData.scrollMaxX); - } - else if (window->type == mvAppItemType::mvTable) + if (DearPyGui::GetApplicableState(window->type) & MV_STATE_SCROLL) { - auto pTable = static_cast(window); - - return ToPyFloat(pTable->_scrollMaxX); + return ToPyFloat(window->state.scrollMax.x); } else { @@ -477,28 +404,13 @@ get_y_scroll_max(PyObject* self, PyObject* args, PyObject* kwargs) return GetPyNone(); } - if (window->type == mvAppItemType::mvWindowAppItem) + if (DearPyGui::GetApplicableState(window->type) & MV_STATE_SCROLL) { - - auto pWindow = static_cast(window); - - return ToPyFloat(pWindow->configData.scrollMaxY); - } - else if (window->type == mvAppItemType::mvChildWindow) - { - auto pChild = static_cast(window); - - return ToPyFloat(pChild->configData.scrollMaxY); - } - else if (window->type == mvAppItemType::mvTable) - { - auto pTable = static_cast(window); - - return ToPyFloat(pTable->_scrollMaxY); + return ToPyFloat(window->state.scrollMax.y); } else { - mvThrowPythonError(mvErrorCode::mvIncompatibleType, "set_y_scroll_max", + mvThrowPythonError(mvErrorCode::mvIncompatibleType, "get_y_scroll_max", "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable", window); } @@ -3730,7 +3642,8 @@ get_item_info(PyObject* self, PyObject* args, PyObject* kwargs) PyDict_SetItemString(pdict, "deactivatedae_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_DEACTIVATEDAE))); PyDict_SetItemString(pdict, "toggled_open_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_TOGGLED_OPEN))); PyDict_SetItemString(pdict, "resized_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_RECT_SIZE))); - + PyDict_SetItemString(pdict, "scroll_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_SCROLL))); + } else diff --git a/src/dearpygui_parsers.h b/src/dearpygui_parsers.h index 0d6d4d745..8446225ed 100644 --- a/src/dearpygui_parsers.h +++ b/src/dearpygui_parsers.h @@ -1784,9 +1784,15 @@ InsertParser_Block4(std::map& parsers) { std::vector args; args.push_back({ mvPyDataType::UUID, "item" }); - args.push_back({ mvPyDataType::Float, "value" }); + args.push_back({ mvPyDataType::Float, "value", mvArgType::REQUIRED_ARG, "", "Scroll position"}); + args.push_back({ mvPyDataType::Integer, "when", mvArgType::KEYWORD_ARG, "internal_dpg.mvSetScrollFlags_Delayed", + "Specifies whether the scroll position will be set in the nearest frame (mvSetScrollFlags_Now) or with " + "a 1-frame delay (mvSetScrollFlags_Delayed). The former prevents flickering, the latter works better " + "if contents change in the same frame as when set_x_scroll called. mvSetScrollFlags_Both can also be used " + "to set the position twice." }); mvPythonParserSetup setup; + setup.about = "Sets horizontal scroll position."; mvPythonParser parser = FinalizeParser(setup, args); parsers.insert({ "set_x_scroll", parser }); @@ -1795,8 +1801,16 @@ InsertParser_Block4(std::map& parsers) { std::vector args; args.push_back({ mvPyDataType::UUID, "item" }); - args.push_back({ mvPyDataType::Float, "value" }); + args.push_back({ mvPyDataType::Float, "value", mvArgType::REQUIRED_ARG, "", "Scroll position"}); + args.push_back({ mvPyDataType::Integer, "when", mvArgType::KEYWORD_ARG, "internal_dpg.mvSetScrollFlags_Delayed", + "Specifies whether the scroll position will be set in the nearest frame (mvSetScrollFlags_Now) or with " + "a 1-frame delay (mvSetScrollFlags_Delayed). The former prevents flickering, the latter works better " + "if contents change in the same frame as when set_x_scroll called. mvSetScrollFlags_Both can also be used " + "to set the position twice." }); + mvPythonParserSetup setup; + setup.about = "Sets vertical scroll position."; + mvPythonParser parser = FinalizeParser(setup, args); parsers.insert({ "set_y_scroll", parser }); } diff --git a/src/mvAppItem.cpp b/src/mvAppItem.cpp index bae62efd6..4815f8797 100644 --- a/src/mvAppItem.cpp +++ b/src/mvAppItem.cpp @@ -258,6 +258,37 @@ mvAppItem::setDataSource(mvUUID value) config.source = value; } +void +mvAppItem::handleImmediateScroll() +{ + if (!((config.scrollXFlags | config.scrollYFlags) & mvSetScrollFlags_Now)) + return; + + ImVec2 scroll = { (config.scrollXFlags & mvSetScrollFlags_Now)? config.scrollX : -1, + (config.scrollYFlags & mvSetScrollFlags_Now)? config.scrollY : -1 }; + ImGui::SetNextWindowScroll(scroll); +} + +void +mvAppItem::handleDelayedScroll() +{ + if (config.scrollXFlags & mvSetScrollFlags_Delayed) + { + if (config.scrollX < 0.0f) + ImGui::SetScrollHereX(1.0f); + else + ImGui::SetScrollX(config.scrollX); + } + + if (config.scrollYFlags & mvSetScrollFlags_Delayed) + { + if (config.scrollY < 0.0f) + ImGui::SetScrollHereY(1.0f); + else + ImGui::SetScrollY(config.scrollY); + } +} + static bool CanItemTypeBeHovered(mvAppItemType type) { @@ -912,6 +943,19 @@ CanItemTypeHaveContAvail(mvAppItemType type) } +static bool +CanItemTypeBeScrolled(mvAppItemType type) +{ + switch (type) + { + case mvAppItemType::mvWindowAppItem: + case mvAppItemType::mvChildWindow: + case mvAppItemType::mvTable: return true; + default: return false; + } + +} + int DearPyGui::GetApplicableState(mvAppItemType type) { @@ -930,6 +974,7 @@ DearPyGui::GetApplicableState(mvAppItemType type) if(CanItemTypeHaveRectMax(type)) applicableState |= MV_STATE_RECT_MAX; if(CanItemTypeHaveRectSize(type)) applicableState |= MV_STATE_RECT_SIZE; if(CanItemTypeHaveContAvail(type)) applicableState |= MV_STATE_CONT_AVAIL; + if(CanItemTypeBeScrolled(type)) applicableState |= MV_STATE_SCROLL; return applicableState; } @@ -1012,6 +1057,7 @@ DearPyGui::GetEntityDesciptionFlags(mvAppItemType type) case mvAppItemType::mvHoverHandler: case mvAppItemType::mvResizeHandler: case mvAppItemType::mvToggledOpenHandler: + case mvAppItemType::mvScrollHandler: case mvAppItemType::mvVisibleHandler: return MV_ITEM_DESC_HANDLER; case mvAppItemType::mvTooltip: @@ -1227,6 +1273,7 @@ DearPyGui::GetAllowableParents(mvAppItemType type) case mvAppItemType::mvHoverHandler: case mvAppItemType::mvResizeHandler: case mvAppItemType::mvToggledOpenHandler: + case mvAppItemType::mvScrollHandler: case mvAppItemType::mvVisibleHandler: MV_START_PARENTS MV_ADD_PARENT(mvAppItemType::mvStage), @@ -1545,7 +1592,8 @@ DearPyGui::GetAllowableChildren(mvAppItemType type) MV_ADD_CHILD(mvAppItemType::mvHoverHandler), MV_ADD_CHILD(mvAppItemType::mvResizeHandler), MV_ADD_CHILD(mvAppItemType::mvToggledOpenHandler), - MV_ADD_CHILD(mvAppItemType::mvVisibleHandler) + MV_ADD_CHILD(mvAppItemType::mvVisibleHandler), + MV_ADD_CHILD(mvAppItemType::mvScrollHandler), MV_END_CHILDREN case mvAppItemType::mvValueRegistry: @@ -5027,6 +5075,19 @@ DearPyGui::GetEntityParser(mvAppItemType type) setup.category = { "Widgets", "Events" }; break; } + case mvAppItemType::mvScrollHandler: + { + AddCommonArgs(args, (CommonParserArgs)( + MV_PARSER_ARG_ID | + MV_PARSER_ARG_SHOW | + MV_PARSER_ARG_PARENT | + MV_PARSER_ARG_CALLBACK) + ); + + setup.about = "Adds a scroll handler."; + setup.category = { "Widgets", "Events" }; + break; + } case mvAppItemType::mvFont: { AddCommonArgs(args, (CommonParserArgs)( diff --git a/src/mvAppItem.h b/src/mvAppItem.h index a0e3a0f0e..aa125b789 100644 --- a/src/mvAppItem.h +++ b/src/mvAppItem.h @@ -109,6 +109,14 @@ struct mvAppItemInfo bool dirtyPos = false; }; +enum mvSetScrollFlags +{ + mvSetScrollFlags_None = 0, + mvSetScrollFlags_Now = 1 << 0, + mvSetScrollFlags_Delayed = 1 << 1, + mvSetScrollFlags_Both = mvSetScrollFlags_Now | mvSetScrollFlags_Delayed +}; + struct mvAppItemConfig { mvUUID source = 0; @@ -134,6 +142,10 @@ struct mvAppItemConfig // 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); + float scrollX = 0.0f; + float scrollY = 0.0f; + mvSetScrollFlags scrollXFlags = mvSetScrollFlags_None; + mvSetScrollFlags scrollYFlags = mvSetScrollFlags_None; }; struct mvAppItemDrawInfo @@ -244,7 +256,12 @@ class mvAppItem : public std::enable_shared_from_this // config setters //----------------------------------------------------------------------------- virtual void setDataSource(mvUUID value); - + + //----------------------------------------------------------------------------- + // scrolling support + //----------------------------------------------------------------------------- + void handleImmediateScroll(); + void handleDelayedScroll(); }; inline bool mvClipPoint(float clipViewport[6], mvVec4& point) @@ -406,6 +423,7 @@ GetEntityCommand(mvAppItemType type) case mvAppItemType::mvDoubleClickedHandler: return "add_item_double_clicked_handler"; case mvAppItemType::mvDragPayload: return "add_drag_payload"; case mvAppItemType::mvResizeHandler: return "add_item_resize_handler"; + case mvAppItemType::mvScrollHandler: return "add_item_scroll_handler"; case mvAppItemType::mvFont: return "add_font"; case mvAppItemType::mvFontRegistry: return "add_font_registry"; case mvAppItemType::mvTheme: return "add_theme"; diff --git a/src/mvAppItemState.cpp b/src/mvAppItemState.cpp index 33a0d5b0b..4c636a5ae 100644 --- a/src/mvAppItemState.cpp +++ b/src/mvAppItemState.cpp @@ -23,6 +23,8 @@ ResetAppItemState(mvAppItemState& state) state.deactivatedAfterEdit = false; state.toggledOpen = false; state.mvRectSizeResized = false; + state.scrolledX = state.scrolledY = false; + state.isScrollingX = state.isScrollingY = false; } void @@ -61,6 +63,17 @@ UpdateAppItemState(mvAppItemState& state) state.mvPrevRectSize = state.rectSize; } +void +UpdateAppItemScrollInfo(mvAppItemState& state) +{ + float scrollX = ImGui::GetScrollX(); + float scrollY = ImGui::GetScrollY(); + state.scrolledX = (scrollX != state.scrollPos.x); + state.scrolledY = (scrollY != state.scrollPos.y); + state.scrollPos = { scrollX, scrollY }; + state.scrollMax = { ImGui::GetScrollMaxX(), ImGui::GetScrollMaxY() }; +} + void FillAppItemState(PyObject* dict, mvAppItemState& state, i32 applicableState) { @@ -97,6 +110,14 @@ FillAppItemState(PyObject* dict, mvAppItemState& state, i32 applicableState) } if(applicableState & MV_STATE_CONT_AVAIL) PyDict_SetItemString(dict, "content_region_avail", mvPyObject(ToPyPairII((i32)state.contextRegionAvail.x, (i32)state.contextRegionAvail.y))); + if(applicableState & MV_STATE_SCROLL) + { + PyDict_SetItemString(dict, "scrolled", mvPyObject(ToPyTPair(valid && state.scrolledX, valid && state.scrolledY))); + PyDict_SetItemString(dict, "is_scrolling", mvPyObject(ToPyTPair(valid && state.isScrollingX, valid && state.isScrollingY))); + PyDict_SetItemString(dict, "scroll_pos", mvPyObject(ToPyPair(state.scrollPos.x, state.scrollPos.y))); + PyDict_SetItemString(dict, "scroll_max", mvPyObject(ToPyPair(state.scrollMax.x, state.scrollMax.y))); + } + } b8 diff --git a/src/mvAppItemState.h b/src/mvAppItemState.h index 691326387..d22d90c41 100644 --- a/src/mvAppItemState.h +++ b/src/mvAppItemState.h @@ -31,8 +31,9 @@ enum mvStateItems MV_STATE_RECT_MAX = 1 << 12, MV_STATE_RECT_SIZE = 1 << 13, MV_STATE_CONT_AVAIL = 1 << 14, + MV_STATE_SCROLL = 1 << 15, MV_STATE_ALL = MV_STATE_HOVER |MV_STATE_ACTIVE |MV_STATE_FOCUSED |MV_STATE_CLICKED |MV_STATE_VISIBLE |MV_STATE_EDITED |MV_STATE_ACTIVATED |MV_STATE_DEACTIVATED |MV_STATE_DEACTIVATEDAE | - MV_STATE_TOGGLED_OPEN | MV_STATE_RECT_MIN |MV_STATE_RECT_MAX |MV_STATE_RECT_SIZE |MV_STATE_CONT_AVAIL + MV_STATE_TOGGLED_OPEN | MV_STATE_RECT_MIN |MV_STATE_RECT_MAX |MV_STATE_RECT_SIZE |MV_STATE_CONT_AVAIL |MV_STATE_SCROLL }; //----------------------------------------------------------------------------- @@ -42,6 +43,10 @@ enum mvStateItems void FillAppItemState (PyObject* dict, mvAppItemState& state, i32 applicableState); // fills python dict with applicable state values void ResetAppItemState (mvAppItemState& state); // reset values to false void UpdateAppItemState(mvAppItemState& state); // standard imgui update +// Retrieves scroll info; only applicable to containers. Does NOT update state.lastFrameUpdate +// and therefore MUST be called in the same branch where that variable is updated +// by the caller (or where UpdateAppItemState() gets called). +void UpdateAppItemScrollInfo(mvAppItemState& state); // return actual value if frame is active b8 IsItemHovered (mvAppItemState& state, i32 frameDelay = 0); @@ -81,12 +86,20 @@ struct mvAppItemState b8 deactivatedAfterEdit = false; b8 toggledOpen = false; b8 mvRectSizeResized = false; + b8 scrolledX = false; + b8 scrolledY = false; + // isScrolling flags are not implemented yet - we need support on ImGui side. + // They are here just as a reserved placeholder for future implementation. + b8 isScrollingX = false; + b8 isScrollingY = false; mvVec2 rectMin = { 0.0f, 0.0f }; mvVec2 rectMax = { 0.0f, 0.0f }; mvVec2 rectSize = { 0.0f, 0.0f }; mvVec2 mvPrevRectSize = { 0.0f, 0.0f }; mvVec2 pos = { 0.0f, 0.0f }; mvVec2 contextRegionAvail = { 0.0f, 0.0f }; + mvVec2 scrollPos = { 0.0f, 0.0f }; + mvVec2 scrollMax = { 0.0f, 0.0f }; b8 ok = true; i32 lastFrameUpdate = 0; // last frame update occured mvAppItem* parent = nullptr; // hacky, but quick fix for widget handlers diff --git a/src/mvAppItemTypes.inc b/src/mvAppItemTypes.inc index 249db0b13..5ecffb450 100644 --- a/src/mvAppItemTypes.inc +++ b/src/mvAppItemTypes.inc @@ -130,6 +130,7 @@ X( mvToggledOpenHandler ) \ X( mvClickedHandler ) \ X( mvDoubleClickedHandler ) \ + X( mvScrollHandler ) \ X( mvDragPayload ) \ X( mvResizeHandler ) \ X( mvFont ) \ diff --git a/src/mvContainers.cpp b/src/mvContainers.cpp index 9b097909b..367c9f39c 100644 --- a/src/mvContainers.cpp +++ b/src/mvContainers.cpp @@ -927,6 +927,8 @@ DearPyGui::draw_child_window(ImDrawList* drawlist, mvAppItem& item, mvChildWindo { ScopedID id(item.uuid); + item.handleImmediateScroll(); + // TODO: Do we want to put an if statement to prevent further drawing if not shown? ImGui::BeginChild(item.info.internalLabel.c_str(), ImVec2(config.autosize_x ? 0 : (float)item.config.width, config.autosize_y ? 0 : (float)item.config.height), config.childFlags, config.windowflags); item.state.lastFrameUpdate = GContext->frame; @@ -951,23 +953,8 @@ DearPyGui::draw_child_window(ImDrawList* drawlist, mvAppItem& item, mvChildWindo } - if (config._scrollXSet) - { - if (config.scrollX < 0.0f) - ImGui::SetScrollHereX(1.0f); - else - ImGui::SetScrollX(config.scrollX); - config._scrollXSet = false; - } - - if (config._scrollYSet) - { - if (config.scrollY < 0.0f) - ImGui::SetScrollHereY(1.0f); - else - ImGui::SetScrollY(config.scrollY); - config._scrollYSet = false; - } + item.handleDelayedScroll(); + item.config.scrollXFlags = item.config.scrollYFlags = mvSetScrollFlags_None; // allows this item to have a render callback if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) @@ -984,10 +971,7 @@ DearPyGui::draw_child_window(ImDrawList* drawlist, mvAppItem& item, mvChildWindo } - config.scrollX = ImGui::GetScrollX(); - config.scrollY = ImGui::GetScrollY(); - config.scrollMaxX = ImGui::GetScrollMaxX(); - config.scrollMaxY = ImGui::GetScrollMaxY(); + UpdateAppItemScrollInfo(item.state); ImGui::EndChild(); } @@ -1502,6 +1486,8 @@ DearPyGui::draw_window(ImDrawList* drawlist, mvAppItem& item, mvWindowAppItemCon ImGui::SetNextWindowSizeConstraints(config.min_size, config.max_size); + item.handleImmediateScroll(); + if (config.modal) { if (item.info.shownLastFrame) @@ -1640,28 +1626,10 @@ DearPyGui::draw_window(ImDrawList* drawlist, mvAppItem& item, mvWindowAppItemCon // handle popping themes cleanup_local_theming(&item); - if (config._scrollXSet) - { - if (config.scrollX < 0.0f) - ImGui::SetScrollHereX(1.0f); - else - ImGui::SetScrollX(config.scrollX); - config._scrollXSet = false; - } - - if (config._scrollYSet) - { - if (config.scrollY < 0.0f) - ImGui::SetScrollHereY(1.0f); - else - ImGui::SetScrollY(config.scrollY); - config._scrollYSet = false; - } + item.handleDelayedScroll(); + item.config.scrollXFlags = item.config.scrollYFlags = mvSetScrollFlags_None; - config.scrollX = ImGui::GetScrollX(); - config.scrollY = ImGui::GetScrollY(); - config.scrollMaxX = ImGui::GetScrollMaxX(); - config.scrollMaxY = ImGui::GetScrollMaxY(); + UpdateAppItemScrollInfo(item.state); //----------------------------------------------------------------------------- // update state diff --git a/src/mvContainers.h b/src/mvContainers.h index 56ed8612c..a5390c7fc 100644 --- a/src/mvContainers.h +++ b/src/mvContainers.h @@ -101,12 +101,6 @@ struct mvChildWindowConfig bool autosize_x = false; bool autosize_y = false; ImGuiWindowFlags windowflags = ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_NavFlattened; - float scrollX = 0.0f; - float scrollY = 0.0f; - float scrollMaxX = 0.0f; - float scrollMaxY = 0.0f; - bool _scrollXSet = false; - bool _scrollYSet = false; }; struct mvTreeNodeConfig @@ -173,13 +167,7 @@ struct mvWindowAppItemConfig mvPyObject on_close = nullptr; mvVec2 min_size = { 100.0f, 100.0f }; mvVec2 max_size = { 30000.0f, 30000.0f }; - float scrollX = 0.0f; - float scrollY = 0.0f; - float scrollMaxX = 0.0f; - float scrollMaxY = 0.0f; bool _collapsedDirty = true; - bool _scrollXSet = false; - bool _scrollYSet = false; ImGuiWindowFlags _oldWindowflags = ImGuiWindowFlags_None; float _oldxpos = 200; float _oldypos = 200; diff --git a/src/mvItemHandlers.cpp b/src/mvItemHandlers.cpp index f8491a475..2c77ac5b7 100644 --- a/src/mvItemHandlers.cpp +++ b/src/mvItemHandlers.cpp @@ -119,6 +119,14 @@ void mvItemHandlerRegistry::onBind(mvAppItem* item) break; } + case mvAppItemType::mvScrollHandler: + { + if (!(applicableState & ~MV_STATE_SCROLL)) + mvThrowPythonError(mvErrorCode::mvNone, "bind_item_handler_registry", + "Item Handler Registry includes inapplicable handler: mvScrollHandler", item); + break; + } + default: break; } } @@ -347,3 +355,45 @@ void mvVisibleHandler::customAction(void* data) submitHandler(state->parent); } } + +PyObject* mvScrollHandler::makeAppData(mvUUID uuid, const std::string& alias, mvScrollDirection dir, bool isScrolling, float pos, float max) +{ + PyObject* app_data = PyTuple_New(5); + PyTuple_SetItem(app_data, 0, ToPyUUID(uuid, alias)); + PyTuple_SetItem(app_data, 1, ToPyInt(dir)); + PyTuple_SetItem(app_data, 2, ToPyFloat(pos)); + PyTuple_SetItem(app_data, 3, ToPyFloat(max)); + PyTuple_SetItem(app_data, 4, ToPyBool(isScrolling)); + return app_data; +} + +void mvScrollHandler::customAction(void* data) +{ + mvAppItemState* state = static_cast(data); + mvAppItem* parent = state->parent; + if (state->scrolledX) + { + submitCallbackEx([=, + uuid=parent->uuid, + alias=parent->config.alias, + isScrolling=state->isScrollingX, + pos=state->scrollPos.x, + max=state->scrollMax.x] () + { + return makeAppData(uuid, alias, mvScrollDirection_XAxis, isScrolling, pos, max); + }); + } + + if (state->scrolledY) + { + submitCallbackEx([=, + uuid=parent->uuid, + alias=parent->config.alias, + isScrolling=state->isScrollingY, + pos=state->scrollPos.y, + max=state->scrollMax.y] () + { + return makeAppData(uuid, alias, mvScrollDirection_YAxis, isScrolling, pos, max); + }); + } +} diff --git a/src/mvItemHandlers.h b/src/mvItemHandlers.h index d32a91d06..08f59bca2 100644 --- a/src/mvItemHandlers.h +++ b/src/mvItemHandlers.h @@ -159,3 +159,23 @@ class mvVisibleHandler : public mvItemHandler void draw(ImDrawList* drawlist, float x, float y) override {} void customAction(void* data = nullptr) override; }; + +enum mvScrollDirection +{ + // These constants can be used as a combination of flags + mvScrollDirection_XAxis = 1, + mvScrollDirection_Horizontal = 1, + mvScrollDirection_YAxis = 2, + mvScrollDirection_Vertical = 2, +}; + +class mvScrollHandler : public mvItemHandler +{ +public: + explicit mvScrollHandler(mvUUID uuid) : mvItemHandler(uuid) {} + void draw(ImDrawList* drawlist, float x, float y) override {} + void customAction(void* data = nullptr) override; + +private: + static PyObject* makeAppData(mvUUID uuid, const std::string& alias, mvScrollDirection dir, bool isScrolling, float pos, float max); +}; diff --git a/src/mvPyUtils.cpp b/src/mvPyUtils.cpp index 2ddd70a45..782bbe684 100644 --- a/src/mvPyUtils.cpp +++ b/src/mvPyUtils.cpp @@ -570,6 +570,17 @@ ToPyPair(const std::string& x, const std::string& y) return Py_BuildValue("[ss]", x.c_str(), y.c_str()); } +PyObject* +ToPyTPair(bool x, bool y) +{ + PyObject* result = PyTuple_New(2); + + PyTuple_SetItem(result, 0, ToPyBool(x)); + PyTuple_SetItem(result, 1, ToPyBool(y)); + + return result; +} + PyObject* ToPyList(const std::vector& value) { diff --git a/src/mvPyUtils.h b/src/mvPyUtils.h index 5e83cbd38..252f71f1d 100644 --- a/src/mvPyUtils.h +++ b/src/mvPyUtils.h @@ -151,6 +151,7 @@ PyObject* ToPyPair (float x, float y); PyObject* ToPyPair (double x, double y); PyObject* ToPyPairII(int x, int y); PyObject* ToPyPair (const std::string& x, const std::string& y); +PyObject* ToPyTPair (bool x, bool y); // tuple-based pair (unlike other pairs that are list-based) PyObject* ToPyList (const std::vector& value); PyObject* ToPyList (const std::vector& value); PyObject* ToPyList (const std::vector& value); diff --git a/src/mvTables.cpp b/src/mvTables.cpp index 4bcd43809..b56df39c0 100644 --- a/src/mvTables.cpp +++ b/src/mvTables.cpp @@ -5,6 +5,7 @@ #include "mvPyUtils.h" #include "mvFontItems.h" #include "mvThemes.h" +#include "mvItemHandlers.h" mvTableCell::mvTableCell(mvUUID uuid) : mvAppItem(uuid) @@ -225,6 +226,8 @@ void mvTable::draw(ImDrawList* drawlist, float x, float y) } }; + handleImmediateScroll(); + if (ImGui::BeginTable(info.internalLabel.c_str(), _columns, _flags, ImVec2((float)config.width, (float)config.height), (float)_inner_width)) { @@ -374,27 +377,11 @@ void mvTable::draw(ImDrawList* drawlist, float x, float y) columnnum++; } - if (_scrollXSet) - { - if (_scrollX < 0.0f) - ImGui::SetScrollHereX(1.0f); - else - ImGui::SetScrollX(_scrollX); - _scrollXSet = false; - } - if (_scrollYSet) - { - if (_scrollY < 0.0f) - ImGui::SetScrollHereY(1.0f); - else - ImGui::SetScrollY(_scrollY); - _scrollYSet = false; - } + // TODO: maybe it should actually go after EndTable + handleDelayedScroll(); + config.scrollXFlags = config.scrollYFlags = mvSetScrollFlags_None; - _scrollX = ImGui::GetScrollX(); - _scrollMaxX = ImGui::GetScrollMaxX(); - _scrollY = ImGui::GetScrollY(); - _scrollMaxY = ImGui::GetScrollMaxY(); + UpdateAppItemScrollInfo(state); ImGui::EndTable(); @@ -414,6 +401,8 @@ void mvTable::draw(ImDrawList* drawlist, float x, float y) // handle popping themes cleanup_local_theming(this); + if (handlerRegistry) + handlerRegistry->checkEvents(&state); } void mvTable::onChildAdd(std::shared_ptr item) diff --git a/src/mvTables.h b/src/mvTables.h index c2d7b19e6..6bf137060 100644 --- a/src/mvTables.h +++ b/src/mvTables.h @@ -76,13 +76,6 @@ class mvTable : public mvAppItem bool _tableHeader = true; bool _useClipper = false; - float _scrollX = 0.0f; - float _scrollY = 0.0f; - float _scrollMaxX = 0.0f; - float _scrollMaxY = 0.0f; - bool _scrollXSet = false; - bool _scrollYSet = false; - std::vector _columnColorsSet; std::vector _rowColorsSet; std::vector _rowSelectionColorsSet; From 572541efba7f6c6ff7596ff2886c27ea905294c7 Mon Sep 17 00:00:00 2001 From: Vladimir Ein Date: Sun, 4 Jan 2026 20:58:32 +0500 Subject: [PATCH 32/58] fix (callbacks): Added initializers to mvCallbackJob fields to calm down PVS Studio (we don't use the default constructor anyway, so it changes nothing but the build process). --- src/mvCallbackRegistry.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mvCallbackRegistry.h b/src/mvCallbackRegistry.h index e8825bbd5..2dc0e21f6 100644 --- a/src/mvCallbackRegistry.h +++ b/src/mvCallbackRegistry.h @@ -198,9 +198,9 @@ struct mvCallbackJob std::weak_ptr owner; // Only valid if `owner` is alive; one must lock() the owner before accessing // the callback. - PyObject* callback; + PyObject* callback = nullptr; std::shared_ptr user_data; - mvUUID sender; + mvUUID sender = 0; std::string alias; std::function app_data_func; // Either `callback` (and `owner`) or `ownerless_callback` must be set, From 77348749a2deeab53209aab185e09d3628acf4d5 Mon Sep 17 00:00:00 2001 From: Vladimir Ein Date: Tue, 6 Jan 2026 01:43:50 +0500 Subject: [PATCH 33/58] fix (tables): fonts bound to table_row do get applied to the row --- src/mvTables.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/mvTables.cpp b/src/mvTables.cpp index b56df39c0..721b7d69e 100644 --- a/src/mvTables.cpp +++ b/src/mvTables.cpp @@ -178,6 +178,12 @@ void mvTable::draw(ImDrawList* drawlist, float x, float y) cleanup_local_theming(prev_visible_row); apply_local_theming(row); + if (row->font) + { + ImFont* fontptr = static_cast(row->font.get())->getFontPtr(); + ImGui::PushFont(fontptr); + } + row->state.lastFrameUpdate = GContext->frame; row->state.visible = true; @@ -224,6 +230,9 @@ void mvTable::draw(ImDrawList* drawlist, float x, float y) cleanup_local_theming(cell.get()); cleanup_local_theming(columnItem.get()); } + + if (row->font) + ImGui::PopFont(); }; handleImmediateScroll(); From 398528a8be98b7b530344f36c9ee3e5d8e4011b2 Mon Sep 17 00:00:00 2001 From: Vladimir Ein Date: Tue, 6 Jan 2026 01:54:20 +0500 Subject: [PATCH 34/58] fix (mvTable): indent property is supported on tables --- src/mvTables.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mvTables.cpp b/src/mvTables.cpp index 721b7d69e..dd91ff17f 100644 --- a/src/mvTables.cpp +++ b/src/mvTables.cpp @@ -153,6 +153,10 @@ void mvTable::draw(ImDrawList* drawlist, float x, float y) if (all_hidden) return; + // set indent + if (config.indent > 0.0f) + ImGui::Indent(config.indent); + // push font if a font object is attached if (font) { @@ -407,6 +411,9 @@ void mvTable::draw(ImDrawList* drawlist, float x, float y) if (font) ImGui::PopFont(); + if (config.indent > 0.0f) + ImGui::Unindent(config.indent); + // handle popping themes cleanup_local_theming(this); From 6747a4d1e0b76a740a2f4177ee792c039ff97ba2 Mon Sep 17 00:00:00 2001 From: Vladimir Ein Date: Wed, 10 Apr 2024 12:27:54 +0500 Subject: [PATCH 35/58] fix (mvTooltip): Event handlers on tooltips do work now (e.g. item_visible handler). --- src/mvBasicWidgets.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mvBasicWidgets.cpp b/src/mvBasicWidgets.cpp index b7b66aae3..6cf58f269 100644 --- a/src/mvBasicWidgets.cpp +++ b/src/mvBasicWidgets.cpp @@ -6255,7 +6255,11 @@ DearPyGui::draw_tooltip(ImDrawList* drawlist, mvAppItem& item) { tooltip->hovered_last_frame = false; item.state.visible = false; + item.state.lastFrameUpdate = GContext->frame; + // TODO: should we reset rectSize and contextRegionAvail? } + if (item.handlerRegistry) + item.handlerRegistry->checkEvents(&item.state); } void From 0d54fdc0fd7b508ec692c7015eb564370311812a Mon Sep 17 00:00:00 2001 From: Vladimir Ein Date: Sat, 23 Sep 2023 00:14:58 +0500 Subject: [PATCH 36/58] fix (mvToolWindow): Bring debug windows to front in show_tool --- src/mvToolManager.cpp | 2 +- src/mvToolWindow.cpp | 6 ++++++ src/mvToolWindow.h | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/mvToolManager.cpp b/src/mvToolManager.cpp index b7cef1100..9494fe43f 100644 --- a/src/mvToolManager.cpp +++ b/src/mvToolManager.cpp @@ -57,7 +57,7 @@ void mvToolManager::ShowTool(mvUUID name) { if (tool->getUUID() == name) { - tool->m_show = true; + tool->m_focusNextFrame = tool->m_show = true; return; } } diff --git a/src/mvToolWindow.cpp b/src/mvToolWindow.cpp index 2914b4c2d..2f991685d 100644 --- a/src/mvToolWindow.cpp +++ b/src/mvToolWindow.cpp @@ -18,6 +18,12 @@ void mvToolWindow::draw() m_dirtyPos = false; } + if (m_focusNextFrame) + { + ImGui::SetNextWindowFocus(); + m_focusNextFrame = false; + } + if (!ImGui::Begin(getTitle(), &m_show, m_windowflags)) { ImGui::End(); diff --git a/src/mvToolWindow.h b/src/mvToolWindow.h index 380b6aeac..7fcbe4aad 100644 --- a/src/mvToolWindow.h +++ b/src/mvToolWindow.h @@ -29,6 +29,7 @@ class mvToolWindow ImGuiWindowFlags m_windowflags = ImGuiWindowFlags_NoSavedSettings; bool m_show = false; + bool m_focusNextFrame = false; bool m_dirtySize = true; bool m_dirtyPos = true; int m_xpos = 200; From 406fd1bdae1cc8118f9eba53f3a8ca4c050a3441 Mon Sep 17 00:00:00 2001 From: Vladimir Ein Date: Wed, 24 Dec 2025 22:23:39 +0500 Subject: [PATCH 37/58] feat (mvLayoutWindow): Reworked the Item Registry tool window quite a bit. --- src/mvLayoutWindow.cpp | 805 +++++++++++++++++++++++++++++++++++------ src/mvLayoutWindow.h | 22 +- src/mvToolWindow.h | 4 +- 3 files changed, 708 insertions(+), 123 deletions(-) diff --git a/src/mvLayoutWindow.cpp b/src/mvLayoutWindow.cpp index 78bec1d96..59261b7ba 100644 --- a/src/mvLayoutWindow.cpp +++ b/src/mvLayoutWindow.cpp @@ -2,6 +2,9 @@ #include #include "mvContext.h" #include "mvItemRegistry.h" +#include "mvFontItems.h" +#include "mvThemes.h" +#include static void DebugItem(const char* label, const char* item) { @@ -10,6 +13,31 @@ DebugItem(const char* label, const char* item) { ImGui::TextColored(ImVec4(1.0f, 0.0f, 1.0f, 1.0f), "%s", item); } +static void +DebugItem(const char* label, float value) { + ImGui::Text("%s", label); + ImGui::SameLine(); + ImGui::TextColored(ImVec4(1.0f, 0.0f, 1.0f, 1.0f), "%g", value); +} + +static void +DebugItem(const char* label, mvVec2 value) { + ImGui::Text("%s", label); + ImGui::SameLine(); + ImGui::TextColored(ImVec4(1.0f, 0.0f, 1.0f, 1.0f), "(%g, %g)", value.x, value.y); +} + +static void +DebugItem(const char* label, bool value) { + DebugItem(label, value? "True" : "False"); +} + +static void InfoHeader(const char* label) { + ImGui::NewLine(); + ImGui::Text(label); + ImGui::Separator(); +} + mvLayoutWindow::mvLayoutWindow() { m_windowflags = ImGuiWindowFlags_NoSavedSettings; @@ -18,11 +46,30 @@ mvLayoutWindow::mvLayoutWindow() void mvLayoutWindow::renderRootCategory(const char* category, std::vector>& roots) { - const auto node_flags = ImGuiTreeNodeFlags_OpenOnArrow | (roots.empty() ? ImGuiTreeNodeFlags_Leaf : 0); + const auto node_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | (roots.empty() ? ImGuiTreeNodeFlags_Leaf : 0); ImGui::PushID(&roots); + + if (_expandToSelected) + { + for (auto& root : roots) + { + if (root->uuid == m_selectedItem || _itemsToExpand.count(root->uuid) > 0) + { + ImGui::SetNextItemOpen(true); + break; + } + } + } + const auto expanded = ImGui::TreeNodeEx(category, node_flags); + if (!roots.empty()) + { + ImGui::SameLine(); + ImGui::TextColored({0.7f, 0.7f, 0.7f, 1.0f}, "(%zd)", roots.size()); + } + if (expanded) { for (auto& root : roots) @@ -38,16 +85,32 @@ void mvLayoutWindow::renderTreeNode(std::shared_ptr& item) // build up flags for current node const auto node_flags = ImGuiTreeNodeFlags_OpenOnArrow + | ImGuiTreeNodeFlags_OpenOnDoubleClick | ((item->uuid == m_selectedItem) ? ImGuiTreeNodeFlags_Selected : 0) | (DearPyGui::GetEntityDesciptionFlags(item->type) & MV_ITEM_DESC_CONTAINER ? 0 : ImGuiTreeNodeFlags_Leaf); // render this node ImGui::PushID(item.get()); - std::string labelToShow = DearPyGui::GetEntityTypeString(item->type); + std::string labelToShow; + // Remember whether we have a custom name for this item, like a label or a tag. + // Otherwise we'll have to show its type name instead, which is not as descriptive. + bool is_labeled = true; if (!item->config.alias.empty()) labelToShow = item->config.alias; else if (!item->config.specifiedLabel.empty()) labelToShow = item->config.specifiedLabel; + else + { + is_labeled = false; + labelToShow = DearPyGui::GetEntityTypeString(item->type); + constexpr char* prefix = "mvAppItemType::"; + // This is a cumbersome way to get string length, but it's the only way + // that provides a constexpr (note: strlen() is not a constexpr). + // Well, we could also do a `sizeof(prefix) - 1`... + constexpr size_t prefix_len = std::char_traits::length(prefix); + if (labelToShow.compare(0, prefix_len, prefix) == 0) + labelToShow.erase(0, prefix_len); + } if (!_imguiFilter.PassFilter(labelToShow.c_str()) && _startFiltering) @@ -56,8 +119,59 @@ void mvLayoutWindow::renderTreeNode(std::shared_ptr& item) return; } + bool expandThisNode = (_expandToSelected && _itemsToExpand.count(item->uuid) > 0); + if (expandThisNode) + { + ImGui::SetNextItemOpen(true); + } + + // TODO: add a flag to GetEntityDesciptionFlags for this + bool is_bindable = (item->type == mvAppItemType::mvTheme || + item->type == mvAppItemType::mvItemHandlerRegistry || + item->type == mvAppItemType::mvFont ); + + // An unused/orhpaned bindable? (note that we might be holding 1 ref to item) + bool is_lonely = (is_bindable && item.use_count() <= (1 + (item == _itemref? 1 : 0))); + + // An extra check for some elements that can have global refs *not* shared + // via shared_ptr. + // - the global theme is the only one that has config.show == true + // - the global font is the only one having _default == true + if (is_lonely && ( + (item->type == mvAppItemType::mvTheme && item->config.show) || + (item->type == mvAppItemType::mvFont && (static_cast(item.get()))->_default))) + { + // It's bound as a global entity and therefore is not lonely + is_lonely = false; + } + + if (is_lonely) + { + ImGui::PushStyleColor(ImGuiCol_Text, {1.0f, 1.0f, 1.0f, 0.5f}); + } + + if (is_labeled) + { + // We're keeping alpha because some sub-trees might be translucent + auto alpha = ImGui::GetStyleColorVec4(ImGuiCol_Text).w; + ImGui::PushStyleColor(ImGuiCol_Text, {0.7f, 0.7f, 1.0f, alpha}); + } + + // Render the node itself const auto expanded = ImGui::TreeNodeEx(labelToShow.c_str(), node_flags); + if (expandThisNode) + { + ImGui::SetScrollHereX(); + ImGui::SetScrollHereY(); + } + + if (is_labeled) + ImGui::PopStyleColor(); + + if (ImGui::IsItemHovered()) + highlightItemRect(item.get()); + if (item->uuid == m_selectedItem) _startFiltering = true; @@ -66,11 +180,18 @@ void mvLayoutWindow::renderTreeNode(std::shared_ptr& item) { m_selectedItem = item->uuid; _itemref = item; - m_dirtyNodes = true; + } + + if (is_lonely) + { + ImGui::SameLine(); + ImGui::Text("(not bound)"); } if (!(DearPyGui::GetEntityDesciptionFlags(item->type) & MV_ITEM_DESC_CONTAINER)) { + if (is_lonely) + ImGui::PopStyleColor(); if(expanded) ImGui::TreePop(); ImGui::PopID(); @@ -85,13 +206,26 @@ void mvLayoutWindow::renderTreeNode(std::shared_ptr& item) int i = 0; for (auto& childrenSet : item->childslots) { - - std::string title = "Child Slot: " + std::to_string(i++); - if (_slots) { + std::string title = "Child Slot: " + std::to_string(i++); + // Only expand a slot if there's something more to expand within it + if (expandThisNode) + { + for (auto& child : childrenSet) + { + if (child && _itemsToExpand.count(child->uuid) > 0) + { + ImGui::SetNextItemOpen(true); + break; + } + } + } - if (ImGui::TreeNodeEx(title.c_str(), childrenSet.empty() ? ImGuiTreeNodeFlags_Leaf : 0)) + ImGui::PushStyleColor(ImGuiCol_Text, {0.7f, 0.7f, 0.7f, 1.0f}); + const auto slot_expanded = ImGui::TreeNodeEx(title.c_str(), childrenSet.empty() ? ImGuiTreeNodeFlags_Leaf : 0); + ImGui::PopStyleColor(); + if (slot_expanded) { for (auto& children : childrenSet) if(children) @@ -109,6 +243,9 @@ void mvLayoutWindow::renderTreeNode(std::shared_ptr& item) ImGui::TreePop(); } + if (is_lonely) + ImGui::PopStyleColor(); + ImGui::PopID(); if (item->uuid == m_selectedItem) @@ -119,22 +256,96 @@ void mvLayoutWindow::renderTreeNode(std::shared_ptr& item) void mvLayoutWindow::drawWidgets() { - mvUUID parentName = 0; + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; - if (_itemref == nullptr && GContext->itemRegistry->windowRoots.size() > 0) - _itemref = GContext->itemRegistry->windowRoots[0]; - else if(_itemref == nullptr) - return; + bool pickerEnabled = _picker; + if (pickerEnabled) + { + ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonHovered)); + } + if (ImGui::Button("Pick...")) + { + _picker = !_picker; + } + if (pickerEnabled) + { + ImGui::PopStyleColor(); + } + if (_picker) + { + mvUUID hoveredItem = getHoveredItem(); + if (hoveredItem) + highlightItemRect(GetItem(*GContext->itemRegistry, hoveredItem)); + + // When picker is enabled, swallow the first click we get, and look at + // what is hovered - but don't abuse our own window. + if (ImGui::IsMouseClicked(0) && !ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + { + // Pretend that nothing happened :) + g.IO.MouseDownDuration[0] = g.IO.MouseDownDurationPrev[0] = -1.0f; + g.IO.MouseDown[0] = g.IO.MouseClicked[0] = false; + // Show that hovered item + jumpToItem(hoveredItem); + _picker = false; + } + + } + + ImGui::SameLine(); - if (_itemref->info.parentPtr) - parentName = _itemref->info.parentPtr->uuid; + if (ImGui::Button("Focused")) + { + if (!jumpToItem(GContext->focusedItem)) + showError("No focused item."); + } - // left side - ImGui::BeginGroup(); + ImGui::SameLine(); + + const char* hint = "tag or uuid"; + ImVec2 hint_size = ImGui::CalcTextSize(hint); + ImGui::SetNextItemWidth(hint_size.x + 2*style.FramePadding.x); + // This input, while it doesn't need a label, *must* use a non-empty ID due to ImGui + // restrictions. That's why we put an explicit ID here. + if (ImGui::InputTextWithHint("###uuid-search", hint, &_search_tag, ImGuiInputTextFlags_AutoSelectAll|ImGuiInputTextFlags_EnterReturnsTrue) + && _search_tag.find_first_not_of(' ') != std::string::npos) + { + mvUUID uuid = GetIdFromAlias(*GContext->itemRegistry, _search_tag); + if (!uuid) + { + try + { + size_t converted = 0; + uuid = std::stoull(_search_tag, &converted); + // Make sure we've used all non-whitespace chars, i.e. the input is a valid + // number and not something like "123die" + if (_search_tag.find_first_not_of(' ', converted) != std::string::npos) + { + // Invalid input - reset it back to 0 + uuid = 0; + } + } + catch (...) + { + uuid = 0; + } + } + if (!jumpToItem(uuid)) + showError("Item not found."); + } + + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + + if (_itemref == nullptr) + { + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImGui::PushStyleColor(ImGuiCol_Text, {1.0f, 1.0f, 1.0f, 0.5f}); + } if (ImGui::ArrowButton("Move Up", ImGuiDir_Up)) { - std::lock_guard lk(GContext->mutex); mvSubmitCallback([&]() { mvPySafeLockGuard lk(GContext->mutex); @@ -145,7 +356,6 @@ void mvLayoutWindow::drawWidgets() ImGui::SameLine(); if (ImGui::ArrowButton("Move Down", ImGuiDir_Down)) { - std::lock_guard lk(GContext->mutex); mvSubmitCallback([&]() { mvPySafeLockGuard lk(GContext->mutex); @@ -155,126 +365,483 @@ void mvLayoutWindow::drawWidgets() ImGui::SameLine(); if (ImGui::Button("Delete")) { - std::lock_guard lk(GContext->mutex); mvSubmitCallback([&]() { mvPySafeLockGuard lk(GContext->mutex); DeleteItem(*GContext->itemRegistry, m_selectedItem, false); m_selectedItem = 0; + resetSelectedItem(); }); - - _itemref = nullptr; - _itemref = GContext->itemRegistry->windowRoots[0]; } ImGui::SameLine(); - if (ImGui::Button("Show")) + if (ImGui::Button("Show") && _itemref != nullptr) { - std::lock_guard lk(GContext->mutex); - mvAppItem* tempItem = GetItem(*GContext->itemRegistry, m_selectedItem); - tempItem->config.show = true; - tempItem->info.shownLastFrame = true; + _itemref->config.show = true; + _itemref->info.shownLastFrame = true; } ImGui::SameLine(); - if (ImGui::Button("Hide")) + if (ImGui::Button("Hide") && _itemref != nullptr) + { + _itemref->config.show = false; + _itemref->info.hiddenLastFrame = true; + } + + if (_itemref == nullptr) { - std::lock_guard lk(GContext->mutex); - mvAppItem* tempItem = GetItem(*GContext->itemRegistry, m_selectedItem); - tempItem->config.show = false; - tempItem->info.hiddenLastFrame = true; + ImGui::PopStyleColor(); + ImGui::PopItemFlag(); } + + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + ImGui::SameLine(); ImGui::Checkbox("Show Slots###layout", &_slots); - ImGui::BeginChild("###layoutwindow", ImVec2(400, 0)); - static char ts[6] = "True"; - static char fs[6] = "False"; - - std::string width = std::to_string(_itemref->config.width); - std::string height = std::to_string(_itemref->config.height); - - std::string sizex = std::to_string(_itemref->state.rectSize.x); - std::string sizey = std::to_string(_itemref->state.rectSize.y); - - ImGui::PushID(_itemref.get()); - DebugItem("Label:", _itemref->config.specifiedLabel.c_str()); - DebugItem("ID:", std::to_string(_itemref->uuid).c_str()); - DebugItem("Alias:", _itemref->config.alias.c_str()); - DebugItem("Type:", DearPyGui::GetEntityTypeString(_itemref->type)); - DebugItem("Filter:", _itemref->config.filter.c_str()); - DebugItem("Payload Type:", _itemref->config.payloadType.c_str()); - DebugItem("Location:", std::to_string(_itemref->info.location).c_str()); - DebugItem("Track Offset:", std::to_string(_itemref->config.trackOffset).c_str()); - DebugItem("Container:", DearPyGui::GetEntityDesciptionFlags(_itemref->type) & MV_ITEM_DESC_CONTAINER ? ts : fs); - DebugItem("Width:", width.c_str()); - DebugItem("Height:", height.c_str()); - DebugItem("Size x:", sizex.c_str()); - DebugItem("Size y:", sizey.c_str()); - DebugItem("Show:", _itemref->config.show ? ts : fs); - 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("Drop Callback:", _itemref->config.dropCallback ? ts : fs); - DebugItem("Drag Callback:", _itemref->config.dragCallback ? ts : fs); - - ImGui::Spacing(); - ImGui::Spacing(); - ImGui::Spacing(); - ImGui::Text("Bindings"); - ImGui::Separator(); - DebugItem("Theme Bound:", _itemref->theme ? ts : fs); - DebugItem("Font Bound:", _itemref->font ? ts : fs); - DebugItem("Handlers Bound:", _itemref->handlerRegistry ? ts : fs); - - int applicableState = DearPyGui::GetApplicableState(_itemref->type); - ImGui::Spacing(); - ImGui::Spacing(); - ImGui::Spacing(); - ImGui::Text("State"); ImGui::Separator(); - if (applicableState & MV_STATE_VISIBLE) DebugItem("Item Visible:", IsItemVisible(_itemref->state, 1) ? ts : fs); - if (applicableState & MV_STATE_HOVER) DebugItem("Item Hovered:", IsItemHovered(_itemref->state, 1) ? ts : fs); - if (applicableState & MV_STATE_ACTIVE) DebugItem("Item Active:", IsItemActive(_itemref->state, 1) ? ts : fs); - if (applicableState & MV_STATE_FOCUSED) DebugItem("Item Focused:", IsItemFocused(_itemref->state, 1) ? ts : fs); - if (applicableState & MV_STATE_CLICKED) - { - DebugItem("Item Left Clicked:", IsItemLeftClicked(_itemref->state, 1) ? ts : fs); - DebugItem("Item Right Clicked:", IsItemRightClicked(_itemref->state, 1) ? ts : fs); - DebugItem("Item Middle Clicked:", IsItemMiddleClicked(_itemref->state, 1) ? ts : fs); - } - if (applicableState & MV_STATE_EDITED) DebugItem("Item Edited:", IsItemEdited(_itemref->state, 1) ? ts : fs); - if (applicableState & MV_STATE_ACTIVATED) DebugItem("Item Activated:", IsItemActivated(_itemref->state, 1) ? ts : fs); - if (applicableState & MV_STATE_DEACTIVATED) DebugItem("Item Deactivated:", IsItemDeactivated(_itemref->state, 1) ? ts : fs); - if (applicableState & MV_STATE_DEACTIVATEDAE) DebugItem("Item DeactivatedAfterEdit:", IsItemDeactivatedAfterEdit(_itemref->state, 1) ? ts : fs); - if (applicableState & MV_STATE_TOGGLED_OPEN) DebugItem("Item ToggledOpen:", IsItemToogledOpen(_itemref->state, 1) ? ts : fs); - ImGui::PopID(); - ImGui::EndChild(); + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(4, 0)); + if (ImGui::BeginTable("", 2, ImGuiTableFlags_Resizable|ImGuiTableFlags_NoSavedSettings|ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_SizingFixedFit|ImGuiTableFlags_NoKeepColumnsVisible)) + { + ImGui::TableSetupColumn(""); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 1.0f); + + ImGui::TableNextRow(); + // left side + ImGui::TableNextColumn(); + + // We want the left side to initially be no narrower than this line + ImVec2 spacer_size = ImGui::CalcTextSize("Type:mvAppItemType::mvWindowAppItem"); + // The left size will initially have a scrollbar, so let's add some space; + // let's also account for item spacing between "type:" and its value (item spacing x), + // and add more padding on the right (let's use frame padding for now). + spacer_size.x += style.ScrollbarSize + style.ItemSpacing.x + style.FramePadding.x; + // No need for vertical offset + spacer_size.y = 0; + // Adding some fake contents that the table will use for auto-fitting + // 1st column (note that the child window below is not used for fitting: + // it itself inherits width from the column). + ImGui::Dummy(spacer_size); + + // We only render item details if there's a selected item OR if + // we've been able to select another item (i.e. there's at least + // something available in the item tree). + if (_itemref != nullptr || resetSelectedItem()) + { + ImGui::BeginChild("###layoutwindow", ImVec2(0, 0)); + static char ts[6] = "True"; + static char fs[6] = "False"; + + std::string width = std::to_string(_itemref->config.width); + std::string height = std::to_string(_itemref->config.height); + + ImGui::PushID(_itemref.get()); + DebugItem("Label:", _itemref->config.specifiedLabel.c_str()); + DebugItem("ID:", std::to_string(_itemref->uuid).c_str()); + DebugItem("Alias:", _itemref->config.alias.c_str()); + DebugItem("Type:", DearPyGui::GetEntityTypeString(_itemref->type)); + + ImGui::Spacing(); + ImGui::Spacing(); + + DebugItem("Show:", _itemref->config.show); + DebugItem("Enabled:", _itemref->config.enabled); + + DebugItem("Pos:", _itemref->state.pos); + DebugItem("Width:", width.c_str()); + DebugItem("Height:", height.c_str()); + + DebugItem("Rect:", _itemref->state.rectMin); + ImGui::SameLine(); + DebugItem("-", _itemref->state.rectMax); + DebugItem("Size:", _itemref->state.rectSize); + + ImGui::Spacing(); + ImGui::Spacing(); + + DebugItem("Container:", DearPyGui::GetEntityDesciptionFlags(_itemref->type) & MV_ITEM_DESC_CONTAINER ? ts : fs); + DebugItem("Location:", std::to_string(_itemref->info.location).c_str()); + DebugItem("Filter:", _itemref->config.filter.c_str()); + + DebugItem("Tracked:", _itemref->config.tracked); + DebugItem("Track Offset:", _itemref->config.trackOffset); + + ImGui::Spacing(); + ImGui::Spacing(); + + DebugItem("Callback:", _itemref->config.callback ? 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); + DebugItem("Payload Type:", _itemref->config.payloadType.c_str()); + + renderTypeSpecificInfo(); + + InfoHeader("Bindings"); + // TODO: make it a hyperlink to the theme + DebugItem("Theme Bound:", _itemref->theme ? ts : fs); + // TODO: make it a hyperlink to the font + DebugItem("Font Bound:", _itemref->font ? ts : fs); + // TODO: make it a hyperlink to the handlers + DebugItem("Handlers Bound:", _itemref->handlerRegistry ? ts : fs); + + int applicableState = DearPyGui::GetApplicableState(_itemref->type); + InfoHeader("State"); + if (applicableState & MV_STATE_VISIBLE) DebugItem("Item Visible:", IsItemVisible(_itemref->state, 1)); + if (applicableState & MV_STATE_HOVER) DebugItem("Item Hovered:", IsItemHovered(_itemref->state, 1)); + if (applicableState & MV_STATE_ACTIVE) DebugItem("Item Active:", IsItemActive(_itemref->state, 1)); + if (applicableState & MV_STATE_FOCUSED) DebugItem("Item Focused:", IsItemFocused(_itemref->state, 1)); + if (applicableState & MV_STATE_CLICKED) + { + DebugItem("Item Left Clicked:", IsItemLeftClicked(_itemref->state, 1)); + DebugItem("Item Right Clicked:", IsItemRightClicked(_itemref->state, 1)); + DebugItem("Item Middle Clicked:", IsItemMiddleClicked(_itemref->state, 1)); + } + if (applicableState & MV_STATE_EDITED) DebugItem("Item Edited:", IsItemEdited(_itemref->state, 1)); + if (applicableState & MV_STATE_ACTIVATED) DebugItem("Item Activated:", IsItemActivated(_itemref->state, 1)); + if (applicableState & MV_STATE_DEACTIVATED) DebugItem("Item Deactivated:", IsItemDeactivated(_itemref->state, 1)); + if (applicableState & MV_STATE_DEACTIVATEDAE) DebugItem("Item DeactivatedAfterEdit:", IsItemDeactivatedAfterEdit(_itemref->state, 1)); + if (applicableState & MV_STATE_TOGGLED_OPEN) DebugItem("Item ToggledOpen:", IsItemToogledOpen(_itemref->state, 1)); + + ImGui::PopID(); + ImGui::EndChild(); + } + + // right side + ImGui::TableNextColumn(); + _imguiFilter.Draw(); + _startFiltering = false; + + if (_expandToSelected) + { + mvAppItem* parent = _itemref->info.parentPtr; + while (parent) + { + _itemsToExpand.insert(parent->uuid); + parent = parent->info.parentPtr; + } + } + + ImGui::BeginChild("TreeChild", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); + renderRootCategory("Windows", GContext->itemRegistry->windowRoots); + renderRootCategory("Themes", GContext->itemRegistry->themeRegistryRoots); + renderRootCategory("Template Registries", GContext->itemRegistry->itemTemplatesRoots); + renderRootCategory("Staging Containers", GContext->itemRegistry->stagingRoots); + renderRootCategory("Texture Registries", GContext->itemRegistry->textureRegistryRoots); + renderRootCategory("Font Registries", GContext->itemRegistry->fontRegistryRoots); + renderRootCategory("Item Handler Registries", GContext->itemRegistry->itemHandlerRegistryRoots); + renderRootCategory("Handler Registries", GContext->itemRegistry->handlerRegistryRoots); + renderRootCategory("Value Registries", GContext->itemRegistry->valueRegistryRoots); + renderRootCategory("Colormap Registries", GContext->itemRegistry->colormapRoots); + renderRootCategory("File Dialogs", GContext->itemRegistry->filedialogRoots); + renderRootCategory("Viewport Menubars", GContext->itemRegistry->viewportMenubarRoots); + renderRootCategory("Viewport Drawlists", GContext->itemRegistry->viewportDrawlistRoots); + ImGui::EndChild(); + + // No longer need to expand the tree + _expandToSelected = false; + _itemsToExpand.clear(); + + ImGui::EndTable(); + } + + ImGui::PopStyleVar(); + renderErrorMessage(); +} - ImGui::EndGroup(); +void mvLayoutWindow::renderThemeComponentInfo(mvAppItem* item) +{ + if (!item || item->type != mvAppItemType::mvThemeComponent) + return; + auto comp = static_cast(item); + DebugItem("Enabled State:", comp->_specificEnabled); + mvAppItemType target_type = (mvAppItemType)comp->_specificType; + DebugItem("Applies to:", (target_type == mvAppItemType::All)? "All item types" : DearPyGui::GetEntityTypeString(target_type)); +} + +void mvLayoutWindow::renderBindCount() +{ + auto count_str = std::to_string(_itemref.use_count() - 2); + DebugItem("Bound to:", count_str.c_str()); ImGui::SameLine(); + ImGui::Text("items"); +} +void mvLayoutWindow::renderTypeSpecificInfo() +{ + switch (_itemref->type) + { + case mvAppItemType::mvTheme: + { + InfoHeader("Theme"); + // Most themes will have False here, so let's not clutter the UI for them + if (_itemref->config.show) + { + DebugItem("Global Theme:", true); + } + renderBindCount(); + } + break; + + case mvAppItemType::mvThemeComponent: + { + InfoHeader("Theme"); + renderThemeComponentInfo(_itemref.get()); + } + break; + + case mvAppItemType::mvThemeColor: + { + InfoHeader("Theme"); + auto item = static_cast(_itemref.get()); + renderThemeComponentInfo(item->info.parentPtr); + + // TODO: add accessors for lib type and color index + // switch (item->_libType) + + // OMG. This is horrible. We might be better off having a typed + // accessor for this, too. + auto color = *static_cast>*>(item->getValue()); + + ImGui::AlignTextToFramePadding(); + ImGui::Text("Color:"); + ImGui::SameLine(); + + // Making it fixed-width so that prefixes do not show up - they look + // messy in that little space the layout window typically has. + float text_width = ImGui::CalcTextSize("255").x; + const ImGuiStyle& style = ImGui::GetStyle(); + float total_width = (text_width <= 0.0f)? -1.0f : 4*(text_width + 2*style.FramePadding.x + style.ItemInnerSpacing.x) + ImGui::GetFrameHeight(); + ImGui::SetNextItemWidth(total_width); + + ImGui::PushStyleColor(ImGuiCol_Text, {1.0f, 0.0f, 1.0f, 1.0f}); + ImGui::ColorEdit4("##color", &(*color)[0], ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoBorder | ImGuiColorEditFlags_NoLabel); + ImGui::PopStyleColor(); + } + break; - // right side - ImGui::BeginGroup(); - _imguiFilter.Draw(); - _startFiltering = false; - ImGui::BeginChild("TreeChild", ImVec2(-1.0f, -1.0f), ImGuiChildFlags_Border); - renderRootCategory("Windows", GContext->itemRegistry->windowRoots); - renderRootCategory("Themes", GContext->itemRegistry->themeRegistryRoots); - renderRootCategory("Template Registries", GContext->itemRegistry->itemTemplatesRoots); - renderRootCategory("Staging Containers", GContext->itemRegistry->stagingRoots); - renderRootCategory("Texture Registries", GContext->itemRegistry->textureRegistryRoots); - renderRootCategory("Font Registries", GContext->itemRegistry->fontRegistryRoots); - renderRootCategory("Item Handler Registries", GContext->itemRegistry->itemHandlerRegistryRoots); - renderRootCategory("Handler Registries", GContext->itemRegistry->handlerRegistryRoots); - renderRootCategory("Value Registries", GContext->itemRegistry->valueRegistryRoots); - renderRootCategory("Colormap Registries", GContext->itemRegistry->colormapRoots); - renderRootCategory("File Dialogs", GContext->itemRegistry->filedialogRoots); - renderRootCategory("Viewport Menubars", GContext->itemRegistry->viewportMenubarRoots); - renderRootCategory("Viewport Drawlists", GContext->itemRegistry->viewportDrawlistRoots); - ImGui::EndChild(); - ImGui::EndGroup(); - - -} \ No newline at end of file + case mvAppItemType::mvThemeStyle: + { + InfoHeader("Theme"); + auto item = static_cast(_itemref.get()); + renderThemeComponentInfo(item->info.parentPtr); + + // TODO: add accessors for lib type and style index + // switch (item->_libType) + + // OMG. This is horrible. We might be better off having a typed + // accessor for this, too. + auto value = *static_cast>*>(item->getValue()); + + ImGui::AlignTextToFramePadding(); + ImGui::Text("Value:"); + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Text, {1.0f, 0.0f, 1.0f, 1.0f}); + float value_buf[2] = {(*value)[0], (*value)[1]}; + if (ImGui::DragFloat2("##style", value_buf, 1.0f, 0.0f, 100.0f, "%g")) + { + (*value)[0] = value_buf[0]; + (*value)[1] = value_buf[1]; + } + ImGui::PopStyleColor(); + } + break; + + case mvAppItemType::mvItemHandlerRegistry: + { + InfoHeader("Handler Registry"); + renderBindCount(); + } + break; + + case mvAppItemType::mvFont: + { + InfoHeader("Font"); + renderBindCount(); + } + break; + } +} + +void mvLayoutWindow::highlightItemRect(mvAppItem* item) +{ + auto rectMin = item->state.rectMin; + auto rectMax = item->state.rectMax; + if (rectMin.x == 0.0f && rectMin.y == 0.0f && rectMax.x == 0.0f && rectMax.y == 0.0f) + { + // This only works for items positioned relative to the viewport, such as + // windows and popups. Some other items, like table columns or child windows, + // will get into this branch too. + + auto pos = item->state.pos; + + // Going through the container chain, adjusting `pos` according to window positions + // that we find across the way to the root window. + mvAppItem* parent = item->info.parentPtr; + while (parent) { + // TODO: if mvTable ever starts saving its position, add it here as well. + // Item positions are relative to the table when scrolling in the table is + // enabled, because in that case it creates an internal child window. + if (parent->type == mvAppItemType::mvWindowAppItem || parent->type == mvAppItemType::mvChildWindow) + pos = pos + parent->state.pos - parent->state.scrollPos; + + parent = parent->info.parentPtr; + } + + ImGui::GetForegroundDrawList()->AddRect(pos, pos + item->state.rectSize, IM_COL32(255, 0, 0, 255)); + } + else + { + // Regular widgets that have rectMin/rectMax go here + ImGui::GetForegroundDrawList()->AddRect(rectMin, rectMax, IM_COL32(255, 255, 0, 255)); + } +} + +bool mvLayoutWindow::jumpToItem(mvUUID item) +{ + auto itemRef = GetRefItem(*GContext->itemRegistry, item); + if (!itemRef) + return false; + m_selectedItem = item; + _itemref = itemRef; + _expandToSelected = (item != 0); + return true; +} + +mvUUID mvLayoutWindow::getHoveredItem() +{ + // Surely this should belong to mvItemRegistry, but let it live here for the moment. + // The layout window will probably be the only user of this function. + + // For simplicity, we'll only check those registries that can actually capture + // hovered state. Note that they are listed here in the reverse order of their + // rendering - this way we'll get the last item that got hovered; see notes in + // findHoveredInSubTree for more info. + std::vector>* categories[] = { + &GContext->itemRegistry->viewportDrawlistRoots, + &GContext->itemRegistry->viewportMenubarRoots, + &GContext->itemRegistry->windowRoots, + &GContext->itemRegistry->filedialogRoots, + }; + for (auto category : categories) + { + mvUUID hoveredItem = findHoveredInCategory(*category); + if (hoveredItem) + return hoveredItem; + } + return 0; +} + +mvUUID mvLayoutWindow::findHoveredInCategory(std::vector>& roots) +{ + for (auto& root : roots) + { + mvUUID hoveredItem = findHoveredInSubTree(root.get()); + if (hoveredItem) + return hoveredItem; + } + return 0; +} + +mvUUID mvLayoutWindow::findHoveredInSubTree(mvAppItem* parent) +{ + // Sometimes there might be null pointers in the widgets tree. + if (!parent) + return 0; + + // Since mvLayoutWindow is rendered at the beginning of a frame, we only + // retain items state from the previous frame, therefore it's already + // 1 frame old - telling IsItemHovered to look back that far. + bool hovered = IsItemHovered(parent->state, 1); + + // If item is hoverable but not hovered, it's definitely not in the chain of hovered + // items. Let's drop the rest of sub-tree. + // + // Note: the way DPG tests for hovered state of windows - not specifying ImGuiHoveredFlags_ChildWindows - + // as soon as a child window is hovered, its parent is reported as "not hovered". + // That's why we can't skip non-hovered windows at all, otherwise we'll miss the + // beginning of the hovered chain. + if (!hovered && (DearPyGui::GetApplicableState(parent->type) & MV_STATE_HOVER) && + parent->type != mvAppItemType::mvWindowAppItem && parent->type != mvAppItemType::mvChildWindow) + return 0; + + // Now, we have three possible cases: + // + // - The item is not hoverable. It may still contain some hovered items in + // its subtree, so we check the children; worst case we'll return 0. + // + // - The item is hoverable (and therefore hovered - see the check above), + // and there's a hovered child inside. We'll search deeper because the + // current item is somewhere in the middle of the chain. + // + // - The item is hovered and it has no hovered children. This means it's + // finally the leaf, that end of the hovered chain that we're looking for. + + // Since slots and children are typicaly rendered in the first-to-last order, + // we're mainly interested in the *last* child that got the hovered status. + // This means we have to do reverse iteration, and have to resort to ugly + // iterators instead of a typical range-for loop. + for (auto itChildSet = std::crbegin(parent->childslots); itChildSet != std::crend(parent->childslots); ++itChildSet) + { + for (auto itChild = std::crbegin(*itChildSet); itChild != std::crend(*itChildSet); ++itChild) + { + mvUUID hoveredItem = findHoveredInSubTree(itChild->get()); + if (hoveredItem) + return hoveredItem; + } + } + // Finally this is the deepest item - or else something totally not hoverable. + return hovered? parent->uuid : 0; +} + +bool mvLayoutWindow::resetSelectedItem() +{ + std::array registries { + &GContext->itemRegistry->windowRoots, + &GContext->itemRegistry->themeRegistryRoots, + &GContext->itemRegistry->itemTemplatesRoots, + &GContext->itemRegistry->stagingRoots, + &GContext->itemRegistry->textureRegistryRoots, + &GContext->itemRegistry->fontRegistryRoots, + &GContext->itemRegistry->itemHandlerRegistryRoots, + &GContext->itemRegistry->handlerRegistryRoots, + &GContext->itemRegistry->valueRegistryRoots, + &GContext->itemRegistry->colormapRoots, + &GContext->itemRegistry->filedialogRoots, + &GContext->itemRegistry->viewportMenubarRoots, + &GContext->itemRegistry->viewportDrawlistRoots, + }; + + for (auto registry : registries) + { + if (!registry->empty()) + { + _itemref = (*registry)[0]; + m_selectedItem = _itemref->uuid; + _expandToSelected = true; + return true; + } + } + + // Just in case it's still holding a ref to an item that no longer exists + // (and thus keeping the item object from deletion). + _itemref = nullptr; + m_selectedItem = 0; + return false; +} + +void mvLayoutWindow::showError(const char* message) +{ + _error_message = message; + ImGui::OpenPopup("error"); +} + +void mvLayoutWindow::renderErrorMessage() +{ + if (ImGui::BeginPopup("error")) + { + ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "%s", _error_message.c_str()); + ImGui::EndPopup(); + } +} diff --git a/src/mvLayoutWindow.h b/src/mvLayoutWindow.h index ab941463b..295e31cbc 100644 --- a/src/mvLayoutWindow.h +++ b/src/mvLayoutWindow.h @@ -2,6 +2,7 @@ #include #include +#include #include "mvToolWindow.h" class mvAppItem; @@ -24,12 +25,29 @@ class mvLayoutWindow final : public mvToolWindow void renderTreeNode(std::shared_ptr& item); void renderRootCategory(const char* category, std::vector>& roots); + void highlightItemRect(mvAppItem* item); + bool jumpToItem(mvUUID item); + void showError(const char* message); + mvUUID getHoveredItem(); + mvUUID findHoveredInSubTree(mvAppItem* parent); + mvUUID findHoveredInCategory(std::vector>& roots); + // Sets _itemref to whatever item it can find, like the first window or + // some other root (e.g. if there are no windows). Returns true if _itemref + // is valid (non-null). Can only return false if there are no any items at all. + bool resetSelectedItem(); + void renderTypeSpecificInfo(); + void renderThemeComponentInfo(mvAppItem* item); + void renderBindCount(); + void renderErrorMessage(); std::shared_ptr _itemref = nullptr; mvUUID m_selectedItem = 0; - bool m_dirtyNodes = true; - int m_selectedId = -1; + bool _expandToSelected = false; + std::unordered_set _itemsToExpand; ImGuiTextFilter _imguiFilter; bool _startFiltering = false; bool _slots = false; + bool _picker = false; + std::string _search_tag; + std::string _error_message; }; \ No newline at end of file diff --git a/src/mvToolWindow.h b/src/mvToolWindow.h index 7fcbe4aad..48916b2d0 100644 --- a/src/mvToolWindow.h +++ b/src/mvToolWindow.h @@ -32,9 +32,9 @@ class mvToolWindow bool m_focusNextFrame = false; bool m_dirtySize = true; bool m_dirtyPos = true; - int m_xpos = 200; + int m_xpos = 150; int m_ypos = 200; - int m_width = 500; + int m_width = 600; int m_height = 500; }; \ No newline at end of file From 1d33ed80c8a9de92cb674a4dc42d89ecabc6e17f Mon Sep 17 00:00:00 2001 From: Vladimir Ein Date: Fri, 12 May 2023 23:56:25 +0500 Subject: [PATCH 38/58] fix: functions called from Python must return null if they raise an exception --- src/dearpygui_commands.h | 714 ++++++++++++++++++++------------------- src/mvPyUtils.cpp | 6 + src/mvPyUtils.h | 1 + 3 files changed, 380 insertions(+), 341 deletions(-) diff --git a/src/dearpygui_commands.h b/src/dearpygui_commands.h index e25355bc2..da838cc46 100644 --- a/src/dearpygui_commands.h +++ b/src/dearpygui_commands.h @@ -24,7 +24,7 @@ bind_colormap(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* sourceraw; if (!Parse((GetParsers())["bind_colormap"], args, kwargs, __FUNCTION__, &itemraw, &sourceraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -36,7 +36,7 @@ bind_colormap(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "bind_colormap", "Item not found: " + std::to_string(item), nullptr); - return GetPyNone(); + return nullptr; } if (source > 15) @@ -46,7 +46,7 @@ bind_colormap(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "bind_colormap", "Source Item not found: " + std::to_string(source), nullptr); - return GetPyNone(); + return nullptr; } if (asource->type == mvAppItemType::mvColorMap) @@ -86,7 +86,7 @@ bind_colormap(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "bind_colormap", "Incompatible type. Expected types include: mvPlot, mvColorMapScale, mvColorMapButton", aitem); - return GetPyNone(); + return nullptr; } @@ -100,7 +100,7 @@ sample_colormap(PyObject* self, PyObject* args, PyObject* kwargs) float t; if (!Parse((GetParsers())["sample_colormap"], args, kwargs, __FUNCTION__, &itemraw, &t)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -113,7 +113,7 @@ sample_colormap(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "sample_colormap", "Source Item not found: " + std::to_string(item), nullptr); - return GetPyNone(); + return nullptr; } if (asource->type == mvAppItemType::mvColorMap) @@ -127,7 +127,7 @@ sample_colormap(PyObject* self, PyObject* args, PyObject* kwargs) if (!GContext->started) { mvThrowPythonError(mvErrorCode::mvNone, "sample_colormap", "This command can only be ran once the app is started.", nullptr); - return GetPyNone(); + return nullptr; } ImVec4 result = ImPlot::SampleColormap(t, (ImPlotColormap)item); @@ -142,7 +142,7 @@ get_colormap_color(PyObject* self, PyObject* args, PyObject* kwargs) int index; if (!Parse((GetParsers())["get_colormap_color"], args, kwargs, __FUNCTION__, &itemraw, &index)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -155,7 +155,7 @@ get_colormap_color(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "get_colormap_color", "Source Item not found: " + std::to_string(item), nullptr); - return GetPyNone(); + return nullptr; } if (asource->type == mvAppItemType::mvColorMap) @@ -177,7 +177,7 @@ get_file_dialog_info(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* file_dialog_raw; if (!Parse((GetParsers())["get_file_dialog_info"], args, kwargs, __FUNCTION__, &file_dialog_raw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -187,13 +187,13 @@ get_file_dialog_info(PyObject* self, PyObject* args, PyObject* kwargs) if (aplot == nullptr) { mvThrowPythonError(mvErrorCode::mvNone, std::to_string(file_dialog) + " plot does not exist."); - return GetPyNone(); + return nullptr; } if (aplot->type != mvAppItemType::mvFileDialog) { mvThrowPythonError(mvErrorCode::mvNone, std::to_string(file_dialog) + " is not a plot."); - return GetPyNone(); + return nullptr; } mvFileDialog* graph = static_cast(aplot); @@ -211,7 +211,7 @@ set_x_scroll(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["set_x_scroll"], args, kwargs, __FUNCTION__, &itemraw, &value, &when)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -222,7 +222,7 @@ set_x_scroll(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "set_x_scroll", "Item not found: " + std::to_string(item), nullptr); - return GetPyNone(); + return nullptr; } if (DearPyGui::GetApplicableState(window->type) & MV_STATE_SCROLL) @@ -234,6 +234,7 @@ set_x_scroll(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "set_x_scroll", "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable", window); + return nullptr; } return GetPyNone(); @@ -249,7 +250,7 @@ set_y_scroll(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["set_y_scroll"], args, kwargs, __FUNCTION__, &itemraw, &value, &when)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -260,7 +261,7 @@ set_y_scroll(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "set_y_scroll", "Item not found: " + std::to_string(item), nullptr); - return GetPyNone(); + return nullptr; } if (DearPyGui::GetApplicableState(window->type) & MV_STATE_SCROLL) @@ -272,6 +273,7 @@ set_y_scroll(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "set_y_scroll", "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable", window); + return nullptr; } return GetPyNone(); @@ -285,7 +287,7 @@ get_x_scroll(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["get_x_scroll"], args, kwargs, __FUNCTION__, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -296,7 +298,7 @@ get_x_scroll(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "get_x_scroll", "Item not found: " + std::to_string(item), nullptr); - return GetPyNone(); + return nullptr; } if (DearPyGui::GetApplicableState(window->type) & MV_STATE_SCROLL) @@ -307,6 +309,7 @@ get_x_scroll(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "get_x_scroll", "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable", window); + return nullptr; } return GetPyNone(); @@ -320,7 +323,7 @@ get_y_scroll(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["get_y_scroll"], args, kwargs, __FUNCTION__, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -331,7 +334,7 @@ get_y_scroll(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "get_y_scroll", "Item not found: " + std::to_string(item), nullptr); - return GetPyNone(); + return nullptr; } if (DearPyGui::GetApplicableState(window->type) & MV_STATE_SCROLL) @@ -342,6 +345,7 @@ get_y_scroll(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "get_y_scroll", "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable", window); + return nullptr; } return GetPyNone(); @@ -355,7 +359,7 @@ get_x_scroll_max(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["get_x_scroll_max"], args, kwargs, __FUNCTION__, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -366,7 +370,7 @@ get_x_scroll_max(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "get_x_scroll_max", "Item not found: " + std::to_string(item), nullptr); - return GetPyNone(); + return nullptr; } if (DearPyGui::GetApplicableState(window->type) & MV_STATE_SCROLL) @@ -377,6 +381,7 @@ get_x_scroll_max(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "get_x_scroll_max", "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable", window); + return nullptr; } return GetPyNone(); @@ -390,7 +395,7 @@ get_y_scroll_max(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["get_y_scroll_max"], args, kwargs, __FUNCTION__, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -401,7 +406,7 @@ get_y_scroll_max(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "get_y_scroll_max", "Item not found: " + std::to_string(item), nullptr); - return GetPyNone(); + return nullptr; } if (DearPyGui::GetApplicableState(window->type) & MV_STATE_SCROLL) @@ -412,6 +417,7 @@ get_y_scroll_max(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "get_y_scroll_max", "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable", window); + return nullptr; } return GetPyNone(); @@ -430,7 +436,7 @@ set_clip_space(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["set_clip_space"], args, kwargs, __FUNCTION__, &itemraw, &topleftx, &toplefty, &width, &height, &mindepth, &maxdepth)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -441,7 +447,7 @@ set_clip_space(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "apply_transform", "Item not found: " + std::to_string(item), nullptr); - return GetPyNone(); + return nullptr; } if (aitem->type == mvAppItemType::mvDrawLayer) @@ -466,7 +472,7 @@ set_clip_space(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "applydrawInfo->transform", "Incompatible type. Expected types include: mvDrawLayer", aitem); - return GetPyNone(); + return nullptr; } @@ -480,7 +486,7 @@ apply_transform(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* transform; if (!Parse((GetParsers())["apply_transform"], args, kwargs, __FUNCTION__, &itemraw, &transform)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -493,7 +499,7 @@ apply_transform(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "apply_transform", "Item not found: " + std::to_string(item), nullptr); - return GetPyNone(); + return nullptr; } if (aitem->type == mvAppItemType::mvDrawNode) @@ -506,7 +512,7 @@ apply_transform(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "apply_transform", "Incompatible type. Expected types include: mvDrawNode", aitem); - return GetPyNone(); + return nullptr; } @@ -521,7 +527,7 @@ create_rotation_matrix(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* axis; if (!Parse((GetParsers())["create_rotation_matrix"], args, kwargs, __FUNCTION__, &angle, &axis)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -547,7 +553,7 @@ create_perspective_matrix(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["create_perspective_matrix"], args, kwargs, __FUNCTION__, &fov, &aspect, &zNear, &zFar)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -573,7 +579,7 @@ create_orthographic_matrix(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["create_orthographic_matrix"], args, kwargs, __FUNCTION__, &left, &right, &bottom, &top, &zNear, &zFar)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -594,7 +600,7 @@ create_translation_matrix(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* axis; if (!Parse((GetParsers())["create_translation_matrix"], args, kwargs, __FUNCTION__, &axis)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -617,7 +623,7 @@ create_scale_matrix(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* axis; if (!Parse((GetParsers())["create_scale_matrix"], args, kwargs, __FUNCTION__, &axis)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -643,7 +649,7 @@ create_lookat_matrix(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["create_lookat_matrix"], args, kwargs, __FUNCTION__, &eye, ¢er, &up)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -671,7 +677,7 @@ create_fps_matrix(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["create_fps_matrix"], args, kwargs, __FUNCTION__, &eye, &pitch, &yaw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -694,7 +700,7 @@ bind_font(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["bind_font"], args, kwargs, __FUNCTION__, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -712,14 +718,14 @@ bind_font(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "bind_font", "Item not found: " + std::to_string(item), nullptr); - return GetPyNone(); + return nullptr; } if (aplot->type != mvAppItemType::mvFont) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "bind_font", "Incompatible type. Expected types include: mvFont", aplot); - return GetPyNone(); + return nullptr; } mvFont* graph = static_cast(aplot); @@ -740,7 +746,7 @@ get_text_size(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["get_text_size"], args, kwargs, __FUNCTION__, &text, &wrap_width, &fontRaw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -760,14 +766,14 @@ get_text_size(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "get_text_size", "Item not found: " + std::to_string(font), nullptr); - return GetPyNone(); + return nullptr; } if (afont->type != mvAppItemType::mvFont) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "get_text_size", "Incompatible type. Expected types include: mvFont", afont); - return GetPyNone(); + return nullptr; } mvFont* graph = static_cast(afont); @@ -806,14 +812,14 @@ get_selected_nodes(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "get_selected_nodes", "Item not found: " + std::to_string(node_editor), nullptr); - return GetPyNone(); + return nullptr; } if (anode_editor->type != mvAppItemType::mvNodeEditor) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "get_selected_nodes", "Incompatible type. Expected types include: mvNodeEditor", anode_editor); - return GetPyNone(); + return nullptr; } mvNodeEditor* editor = static_cast(anode_editor); @@ -841,14 +847,14 @@ get_selected_links(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "get_selected_links", "Item not found: " + std::to_string(node_editor), nullptr); - return GetPyNone(); + return nullptr; } if (anode_editor->type != mvAppItemType::mvNodeEditor) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "get_selected_links", "Incompatible type. Expected types include: mvNodeEditor", anode_editor); - return GetPyNone(); + return nullptr; } mvNodeEditor* editor = static_cast(anode_editor); @@ -875,14 +881,14 @@ clear_selected_links(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "clear_selected_links", "Item not found: " + std::to_string(node_editor), nullptr); - return GetPyNone(); + return nullptr; } if (anode_editor->type != mvAppItemType::mvNodeEditor) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "clear_selected_links", "Incompatible type. Expected types include: mvNodeEditor", anode_editor); - return GetPyNone(); + return nullptr; } mvNodeEditor* editor = static_cast(anode_editor); @@ -909,14 +915,14 @@ clear_selected_nodes(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "clear_selected_nodes", "Item not found: " + std::to_string(node_editor), nullptr); - return GetPyNone(); + return nullptr; } if (anode_editor->type != mvAppItemType::mvNodeEditor) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "clear_selected_nodes", "Incompatible type. Expected types include: mvNodeEditor", anode_editor); - return GetPyNone(); + return nullptr; } mvNodeEditor* editor = static_cast(anode_editor); @@ -933,7 +939,7 @@ get_plot_query_rects(PyObject* self, PyObject* args, PyObject* kwargs) auto tag = "get_plot_query_rects"; if (!Parse((GetParsers())[tag], args, kwargs, __FUNCTION__, &plotraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -943,13 +949,13 @@ get_plot_query_rects(PyObject* self, PyObject* args, PyObject* kwargs) if (aplot == nullptr) { mvThrowPythonError(mvErrorCode::mvItemNotFound, tag, "Item not found: " + std::to_string(plot), nullptr); - return GetPyNone(); + return nullptr; } if (aplot->type != mvAppItemType::mvPlot) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, tag, "Incompatible type. Expected types include: mvPlot", aplot); - return GetPyNone(); + return nullptr; } mvPlot* graph = static_cast(aplot); @@ -971,7 +977,7 @@ set_axis_ticks(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* label_pairs; if (!Parse((GetParsers())["set_axis_ticks"], args, kwargs, __FUNCTION__, &plotraw, &label_pairs)) - return GetPyNone(); + return nullptr; auto mlabel_pairs = ToVectPairStringFloat(label_pairs); @@ -984,14 +990,14 @@ set_axis_ticks(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "set_axis_ticks", "Item not found: " + std::to_string(plot), nullptr); - return GetPyNone(); + return nullptr; } if (aplot->type != mvAppItemType::mvPlotAxis) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "set_axis_ticks", "Incompatible type. Expected types include: mvPlotAxis", aplot); - return GetPyNone(); + return nullptr; } mvPlotAxis* graph = static_cast(aplot); @@ -1165,7 +1171,7 @@ set_axis_limits(PyObject* self, PyObject* args, PyObject* kwargs) float ymax; if (!Parse((GetParsers())["set_axis_limits"], args, kwargs, __FUNCTION__, &axisraw, &ymin, &ymax)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1176,14 +1182,14 @@ set_axis_limits(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "set_axis_limits", "Item not found: " + std::to_string(axis), nullptr); - return GetPyNone(); + return nullptr; } if (aplot->type != mvAppItemType::mvPlotAxis) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "set_axis_limits", "Incompatible type. Expected types include: mvPlotAxis", aplot); - return GetPyNone(); + return nullptr; } mvPlotAxis* graph = static_cast(aplot); @@ -1198,7 +1204,7 @@ set_axis_limits_auto(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* axisraw; if (!Parse((GetParsers())["set_axis_limits_auto"], args, kwargs, __FUNCTION__, &axisraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1209,14 +1215,14 @@ set_axis_limits_auto(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "set_axis_limits", "Item not found: " + std::to_string(axis), nullptr); - return GetPyNone(); + return nullptr; } if (aplot->type != mvAppItemType::mvPlotAxis) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "set_axis_limits", "Incompatible type. Expected types include: mvPlotAxis", aplot); - return GetPyNone(); + return nullptr; } mvPlotAxis* graph = static_cast(aplot); @@ -1232,7 +1238,7 @@ fit_axis_data(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* axisraw; if (!Parse((GetParsers())["fit_axis_data"], args, kwargs, __FUNCTION__, &axisraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1243,14 +1249,14 @@ fit_axis_data(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "fit_axis_data", "Item not found: " + std::to_string(axis), nullptr); - return GetPyNone(); + return nullptr; } if (aplot->type != mvAppItemType::mvPlotAxis) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "fit_axis_data", "Incompatible type. Expected types include: mvPlotAxis", aplot); - return GetPyNone(); + return nullptr; } mvPlotAxis* graph = static_cast(aplot); @@ -1268,7 +1274,7 @@ get_axis_limits(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* plotraw; if (!Parse((GetParsers())["get_axis_limits"], args, kwargs, __FUNCTION__, &plotraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1279,14 +1285,14 @@ get_axis_limits(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "get_axis_limits", "Item not found: " + std::to_string(plot), nullptr); - return GetPyNone(); + return nullptr; } if (aplot->type != mvAppItemType::mvPlotAxis) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "get_axis_limits", "Incompatible type. Expected types include: mvPlotAxis", aplot); - return GetPyNone(); + return nullptr; } mvPlotAxis* graph = static_cast(aplot); @@ -1301,7 +1307,7 @@ reset_axis_ticks(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* plotraw; if (!Parse((GetParsers())["reset_axis_ticks"], args, kwargs, __FUNCTION__, &plotraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1312,14 +1318,14 @@ reset_axis_ticks(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "reset_axis_ticks", "Item not found: " + std::to_string(plot), nullptr); - return GetPyNone(); + return nullptr; } if (aplot->type != mvAppItemType::mvPlotAxis) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "reset_axis_ticks", "Incompatible type. Expected types include: mvPlotAxis", aplot); - return GetPyNone(); + return nullptr; } mvPlotAxis* graph = static_cast(aplot); @@ -1339,7 +1345,7 @@ highlight_table_column(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* color; if (!Parse((GetParsers())["highlight_table_column"], args, kwargs, __FUNCTION__, &tableraw, &column, &color)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1350,14 +1356,14 @@ highlight_table_column(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "highlight_table_column", "Item not found: " + std::to_string(table), nullptr); - return GetPyNone(); + return nullptr; } if (atable->type != mvAppItemType::mvTable) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "highlight_table_column", "Incompatible type. Expected types include: mvTable", atable); - return GetPyNone(); + return nullptr; } mvTable* tablecast = static_cast(atable); @@ -1366,7 +1372,7 @@ highlight_table_column(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvNone, "highlight_table_column", "Column out of range", tablecast); - return GetPyNone(); + return nullptr; } mvColor finalColor = ToColor(color); @@ -1383,7 +1389,7 @@ unhighlight_table_column(PyObject* self, PyObject* args, PyObject* kwargs) int column = 0; if (!Parse((GetParsers())["unhighlight_table_column"], args, kwargs, __FUNCTION__, &tableraw, &column)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1394,14 +1400,14 @@ unhighlight_table_column(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "unhighlight_table_column", "Item not found: " + std::to_string(table), nullptr); - return GetPyNone(); + return nullptr; } if (atable->type != mvAppItemType::mvTable) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "unhighlight_table_column", "Incompatible type. Expected types include: mvTable", atable); - return GetPyNone(); + return nullptr; } mvTable* tablecast = static_cast(atable); @@ -1410,7 +1416,7 @@ unhighlight_table_column(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvNone, "unhighlight_table_column", "Column out of range", tablecast); - return GetPyNone(); + return nullptr; } tablecast->_columnColorsSet[column] = false; @@ -1426,7 +1432,7 @@ set_table_row_color(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* color; if (!Parse((GetParsers())["set_table_row_color"], args, kwargs, __FUNCTION__, &tableraw, &row, &color)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1437,14 +1443,14 @@ set_table_row_color(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "set_table_row_color", "Item not found: " + std::to_string(table), nullptr); - return GetPyNone(); + return nullptr; } if (atable->type != mvAppItemType::mvTable) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "set_table_row_color", "Incompatible type. Expected types include: mvTable", atable); - return GetPyNone(); + return nullptr; } mvTable* tablecast = static_cast(atable); @@ -1453,7 +1459,7 @@ set_table_row_color(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvNone, "set_table_row_color", "Row out of range", tablecast); - return GetPyNone(); + return nullptr; } mvColor finalColor = ToColor(color); @@ -1470,7 +1476,7 @@ unset_table_row_color(PyObject* self, PyObject* args, PyObject* kwargs) int row = 0; if (!Parse((GetParsers())["unset_table_row_color"], args, kwargs, __FUNCTION__, &tableraw, &row)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1481,14 +1487,14 @@ unset_table_row_color(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "unset_table_row_color", "Item not found: " + std::to_string(table), nullptr); - return GetPyNone(); + return nullptr; } if (atable->type != mvAppItemType::mvTable) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "unset_table_row_color", "Incompatible type. Expected types include: mvTable", atable); - return GetPyNone(); + return nullptr; } mvTable* tablecast = static_cast(atable); @@ -1497,7 +1503,7 @@ unset_table_row_color(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvNone, "set_table_row_color", "Row out of range", tablecast); - return GetPyNone(); + return nullptr; } tablecast->_rowColorsSet[row] = false; @@ -1512,7 +1518,7 @@ highlight_table_row(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* color; if (!Parse((GetParsers())["highlight_table_row"], args, kwargs, __FUNCTION__, &tableraw, &row, &color)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1523,14 +1529,14 @@ highlight_table_row(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "highlight_table_row", "Item not found: " + std::to_string(table), nullptr); - return GetPyNone(); + return nullptr; } if (atable->type != mvAppItemType::mvTable) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "highlight_table_row", "Incompatible type. Expected types include: mvTable", atable); - return GetPyNone(); + return nullptr; } mvTable* tablecast = static_cast(atable); @@ -1539,7 +1545,7 @@ highlight_table_row(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvNone, "highlight_table_row", "Row out of range", tablecast); - return GetPyNone(); + return nullptr; } mvColor finalColor = ToColor(color); @@ -1556,7 +1562,7 @@ unhighlight_table_row(PyObject* self, PyObject* args, PyObject* kwargs) int row = 0; if (!Parse((GetParsers())["unhighlight_table_row"], args, kwargs, __FUNCTION__, &tableraw, &row)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1567,14 +1573,14 @@ unhighlight_table_row(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "unhighlight_table_row", "Item not found: " + std::to_string(table), nullptr); - return GetPyNone(); + return nullptr; } if (atable->type != mvAppItemType::mvTable) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "unhighlight_table_row", "Incompatible type. Expected types include: mvTable", atable); - return GetPyNone(); + return nullptr; } mvTable* tablecast = static_cast(atable); @@ -1583,7 +1589,7 @@ unhighlight_table_row(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvNone, "unselect_table_row", "Row out of range", tablecast); - return GetPyNone(); + return nullptr; } tablecast->_rowSelectionColorsSet[row] = false; @@ -1600,7 +1606,7 @@ highlight_table_cell(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* color; if (!Parse((GetParsers())["highlight_table_cell"], args, kwargs, __FUNCTION__, &tableraw, &row, &column, &color)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1611,14 +1617,14 @@ highlight_table_cell(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "highlight_table_cell", "Item not found: " + std::to_string(table), nullptr); - return GetPyNone(); + return nullptr; } if (atable->type != mvAppItemType::mvTable) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "highlight_table_cell", "Incompatible type. Expected types include: mvTable", atable); - return GetPyNone(); + return nullptr; } mvTable* tablecast = static_cast(atable); @@ -1627,7 +1633,7 @@ highlight_table_cell(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvNone, "highlight_table_cell", "Row/Column out of range", tablecast); - return GetPyNone(); + return nullptr; } mvColor finalColor = ToColor(color); @@ -1645,7 +1651,7 @@ unhighlight_table_cell(PyObject* self, PyObject* args, PyObject* kwargs) int column = 0; if (!Parse((GetParsers())["unhighlight_table_cell"], args, kwargs, __FUNCTION__, &tableraw, &row, &column)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1656,14 +1662,14 @@ unhighlight_table_cell(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "unhighlight_table_cell", "Item not found: " + std::to_string(table), nullptr); - return GetPyNone(); + return nullptr; } if (atable->type != mvAppItemType::mvTable) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "unhighlight_table_cell", "Incompatible type. Expected types include: mvTable", atable); - return GetPyNone(); + return nullptr; } mvTable* tablecast = static_cast(atable); @@ -1672,7 +1678,7 @@ unhighlight_table_cell(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvNone, "unhighlight_table_cell", "Row/Column out of range", tablecast); - return GetPyNone(); + return nullptr; } tablecast->_cellColorsSet[row][column] = false; @@ -1688,7 +1694,7 @@ is_table_cell_highlighted(PyObject* self, PyObject* args, PyObject* kwargs) int column = 0; if (!Parse((GetParsers())["is_table_cell_highlighted"], args, kwargs, __FUNCTION__, &tableraw, &row, &column)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1699,14 +1705,14 @@ is_table_cell_highlighted(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "is_table_cell_highlighted", "Item not found: " + std::to_string(table), nullptr); - return GetPyNone(); + return nullptr; } if (atable->type != mvAppItemType::mvTable) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "is_table_cell_highlighted", "Incompatible type. Expected types include: mvTable", atable); - return GetPyNone(); + return nullptr; } mvTable* tablecast = static_cast(atable); @@ -1715,7 +1721,7 @@ is_table_cell_highlighted(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvNone, "is_table_cell_highlighted", "Row/Column out of range", tablecast); - return GetPyNone(); + return nullptr; } if (tablecast->_cellColorsSet[row][column]) @@ -1735,7 +1741,7 @@ is_table_row_highlighted(PyObject* self, PyObject* args, PyObject* kwargs) int row = 0; if (!Parse((GetParsers())["is_table_row_highlighted"], args, kwargs, __FUNCTION__, &tableraw, &row)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1746,14 +1752,14 @@ is_table_row_highlighted(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "is_table_row_highlighted", "Item not found: " + std::to_string(table), nullptr); - return GetPyNone(); + return nullptr; } if (atable->type != mvAppItemType::mvTable) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "is_table_row_highlighted", "Incompatible type. Expected types include: mvTable", atable); - return GetPyNone(); + return nullptr; } mvTable* tablecast = static_cast(atable); @@ -1762,7 +1768,7 @@ is_table_row_highlighted(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvNone, "is_table_row_highlighted", "Row out of range", tablecast); - return GetPyNone(); + return nullptr; } return ToPyBool(tablecast->_rowSelectionColorsSet[row]); @@ -1775,7 +1781,7 @@ is_table_column_highlighted(PyObject* self, PyObject* args, PyObject* kwargs) int column = 0; if (!Parse((GetParsers())["is_table_column_highlighted"], args, kwargs, __FUNCTION__, &tableraw, &column)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1786,14 +1792,14 @@ is_table_column_highlighted(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "is_table_column_highlighted", "Item not found: " + std::to_string(table), nullptr); - return GetPyNone(); + return nullptr; } if (atable->type != mvAppItemType::mvTable) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "is_table_column_highlighted", "Incompatible type. Expected types include: mvTable", atable); - return GetPyNone(); + return nullptr; } mvTable* tablecast = static_cast(atable); @@ -1802,7 +1808,7 @@ is_table_column_highlighted(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvNone, "is_table_column_highlighted", "Column out of range", tablecast); - return GetPyNone(); + return nullptr; } return ToPyBool(tablecast->_columnColorsSet[column]); @@ -1816,7 +1822,7 @@ bind_theme(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["bind_theme"], args, kwargs, __FUNCTION__, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1834,14 +1840,14 @@ bind_theme(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "bind_theme", "Item not found: " + std::to_string(item), nullptr); - return GetPyNone(); + return nullptr; } if (aplot->type != mvAppItemType::mvTheme) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "bind_theme", "Incompatible type. Expected types include: mvTheme", aplot); - return GetPyNone(); + return nullptr; } mvTheme* graph = static_cast(aplot); @@ -1859,7 +1865,7 @@ set_global_font_scale(PyObject* self, PyObject* args, PyObject* kwargs) float scale; if (!Parse((GetParsers())["set_global_font_scale"], args, kwargs, __FUNCTION__, &scale)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); mvToolManager::GetFontManager().setGlobalFontScale(scale); @@ -1880,7 +1886,7 @@ show_tool(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["show_tool"], args, kwargs, __FUNCTION__, &toolraw)) - return GetPyNone(); + return nullptr; mvUUID tool = GetIDFromPyObject(toolraw); @@ -1916,7 +1922,7 @@ set_frame_callback(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["set_frame_callback"], args, kwargs, __FUNCTION__, &frame, &callback, &user_data)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1937,7 +1943,7 @@ set_exit_callback(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["set_exit_callback"], args, kwargs, __FUNCTION__, &callback, &user_data)) - return GetPyNone(); + return nullptr; *GContext->callbackRegistry->onCloseCallback = mvPyObject(callback == Py_None? nullptr : callback, true); *GContext->callbackRegistry->onCloseCallbackUserData = mvPyObject(user_data, true); @@ -1953,7 +1959,7 @@ set_viewport_resize_callback(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["set_viewport_resize_callback"], args, kwargs, __FUNCTION__, &callback, &user_data)) - return GetPyNone(); + return nullptr; *GContext->callbackRegistry->resizeCallback = mvPyObject(callback == Py_None? nullptr : callback, true); *GContext->callbackRegistry->resizeCallbackUserData = mvPyObject(user_data, true); @@ -1967,33 +1973,34 @@ get_viewport_configuration(PyObject* self, PyObject* args, PyObject* kwargs) mvPySafeLockGuard lk(GContext->mutex); - PyObject* pdict = PyDict_New(); mvViewport* viewport = GContext->viewport; - if (viewport) + if (!viewport) { - PyDict_SetItemString(pdict, "clear_color", mvPyObject(ToPyColor(viewport->clearColor))); - PyDict_SetItemString(pdict, "small_icon", mvPyObject(ToPyString(viewport->small_icon))); - PyDict_SetItemString(pdict, "large_icon", mvPyObject(ToPyString(viewport->large_icon))); - PyDict_SetItemString(pdict, "x_pos", mvPyObject(ToPyInt(viewport->xpos))); - PyDict_SetItemString(pdict, "y_pos", mvPyObject(ToPyInt(viewport->ypos))); - PyDict_SetItemString(pdict, "width", mvPyObject(ToPyInt(viewport->actualWidth))); - PyDict_SetItemString(pdict, "height", mvPyObject(ToPyInt(viewport->actualHeight))); - PyDict_SetItemString(pdict, "client_width", mvPyObject(ToPyInt(viewport->clientWidth))); - PyDict_SetItemString(pdict, "client_height", mvPyObject(ToPyInt(viewport->clientHeight))); - PyDict_SetItemString(pdict, "resizable", mvPyObject(ToPyBool(viewport->resizable))); - PyDict_SetItemString(pdict, "vsync", mvPyObject(ToPyBool(viewport->vsync))); - PyDict_SetItemString(pdict, "min_width", mvPyObject(ToPyInt(viewport->minwidth))); - PyDict_SetItemString(pdict, "max_width", mvPyObject(ToPyInt(viewport->maxwidth))); - PyDict_SetItemString(pdict, "min_height", mvPyObject(ToPyInt(viewport->minheight))); - PyDict_SetItemString(pdict, "max_height", mvPyObject(ToPyInt(viewport->maxheight))); - PyDict_SetItemString(pdict, "always_on_top", mvPyObject(ToPyBool(viewport->alwaysOnTop))); - PyDict_SetItemString(pdict, "decorated", mvPyObject(ToPyBool(viewport->decorated))); - PyDict_SetItemString(pdict, "title", mvPyObject(ToPyString(viewport->title))); - PyDict_SetItemString(pdict, "disable_close", mvPyObject(ToPyBool(viewport->disableClose))); - } - else mvThrowPythonError(mvErrorCode::mvNone, "No viewport created"); + return nullptr; + } + + PyObject* pdict = PyDict_New(); + + PyDict_SetItemString(pdict, "clear_color", mvPyObject(ToPyColor(viewport->clearColor))); + PyDict_SetItemString(pdict, "small_icon", mvPyObject(ToPyString(viewport->small_icon))); + PyDict_SetItemString(pdict, "large_icon", mvPyObject(ToPyString(viewport->large_icon))); + PyDict_SetItemString(pdict, "x_pos", mvPyObject(ToPyInt(viewport->xpos))); + PyDict_SetItemString(pdict, "y_pos", mvPyObject(ToPyInt(viewport->ypos))); + PyDict_SetItemString(pdict, "width", mvPyObject(ToPyInt(viewport->actualWidth))); + PyDict_SetItemString(pdict, "height", mvPyObject(ToPyInt(viewport->actualHeight))); + PyDict_SetItemString(pdict, "client_width", mvPyObject(ToPyInt(viewport->clientWidth))); + PyDict_SetItemString(pdict, "client_height", mvPyObject(ToPyInt(viewport->clientHeight))); + PyDict_SetItemString(pdict, "resizable", mvPyObject(ToPyBool(viewport->resizable))); + PyDict_SetItemString(pdict, "vsync", mvPyObject(ToPyBool(viewport->vsync))); + PyDict_SetItemString(pdict, "min_width", mvPyObject(ToPyInt(viewport->minwidth))); + PyDict_SetItemString(pdict, "max_width", mvPyObject(ToPyInt(viewport->maxwidth))); + PyDict_SetItemString(pdict, "min_height", mvPyObject(ToPyInt(viewport->minheight))); + PyDict_SetItemString(pdict, "max_height", mvPyObject(ToPyInt(viewport->maxheight))); + PyDict_SetItemString(pdict, "always_on_top", mvPyObject(ToPyBool(viewport->alwaysOnTop))); + PyDict_SetItemString(pdict, "decorated", mvPyObject(ToPyBool(viewport->decorated))); + PyDict_SetItemString(pdict, "title", mvPyObject(ToPyString(viewport->title))); return pdict; } @@ -2046,7 +2053,7 @@ create_viewport(PyObject* self, PyObject* args, PyObject* kwargs) &title, &small_icon, &large_icon, &width, &height, &x_pos, &y_pos, &min_width, &max_width, &min_height, &max_height, &resizable, &vsync, &always_on_top, &decorated, &color, &disable_close )) - return GetPyNone(); + return nullptr; mvViewport* viewport = mvCreateViewport(); if (PyObject* item = PyDict_GetItemString(kwargs, "clear_color")) viewport->clearColor = ToColor(item); @@ -2080,7 +2087,7 @@ show_viewport(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["show_viewport"], args, kwargs, __FUNCTION__, &minimized, &maximized)) - return GetPyNone(); + return nullptr; mvViewport* viewport = GContext->viewport; if (viewport) @@ -2090,7 +2097,11 @@ show_viewport(PyObject* self, PyObject* args, PyObject* kwargs) viewport->shown = true; } else + { mvThrowPythonError(mvErrorCode::mvNone, "No viewport created"); + return nullptr; + } + return GetPyNone(); } @@ -2123,7 +2134,10 @@ configure_viewport(PyObject* self, PyObject* args, PyObject* kwargs) } else + { mvThrowPythonError(mvErrorCode::mvNone, "No viewport created"); + return nullptr; + } return GetPyNone(); } @@ -2170,12 +2184,15 @@ save_init_file(PyObject* self, PyObject* args, PyObject* kwargs) const char* file; if (!Parse((GetParsers())["save_init_file"], args, kwargs, __FUNCTION__, &file)) - return GetPyNone(); + return nullptr; if (GContext->started) ImGui::SaveIniSettingsToDisk(file); else + { mvThrowPythonError(mvErrorCode::mvNone, "Dear PyGui must be started to use \"save_init_file\"."); + return nullptr; + } return GetPyNone(); } @@ -2184,7 +2201,7 @@ static PyObject* split_frame(PyObject* self, PyObject* args, PyObject* kwargs) { if (!Parse((GetParsers())["split_frame"], args, kwargs, __FUNCTION__)) - return GetPyNone(); + return nullptr; if (GContext->running) { @@ -2243,7 +2260,7 @@ load_image(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["load_image"], args, kwargs, __FUNCTION__, &file, &gamma, &gamma_scale)) - return GetPyNone(); + return nullptr; // Vout = (Vin / 255)^v; Where v = gamma @@ -2314,7 +2331,7 @@ save_image(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["save_image"], args, kwargs, __FUNCTION__, &file, &width, &height, &data, &components, &quality)) - return GetPyNone(); + return nullptr; enum ImageType_ { @@ -2338,19 +2355,19 @@ save_image(PyObject* self, PyObject* args, PyObject* kwargs) if (filepathLength < 5) { mvThrowPythonError(mvErrorCode::mvNone, "File path for 'save_image(...)' must be of the form 'name.png'."); - return GetPyNone(); + return nullptr; } if (components > 4 || components < 1) { mvThrowPythonError(mvErrorCode::mvNone, "Component count for 'save_image(...)' must be between 1 and 4."); - return GetPyNone(); + return nullptr; } if (quality < 1 || quality > 100) { mvThrowPythonError(mvErrorCode::mvNone, "Quality must be between 1 and 100."); - return GetPyNone(); + return nullptr; } // TODO: support other formats @@ -2377,7 +2394,7 @@ save_image(PyObject* self, PyObject* args, PyObject* kwargs) else { mvThrowPythonError(mvErrorCode::mvNone, "File path for 'save_image(...)' must be of the form 'name.png'."); - return GetPyNone(); + return nullptr; } switch (imageType) @@ -2426,7 +2443,7 @@ output_frame_buffer(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["output_frame_buffer"], args, kwargs, __FUNCTION__, &file, &callback)) - return GetPyNone(); + return nullptr; size_t filepathLength = strlen(file); @@ -2464,7 +2481,7 @@ output_frame_buffer(PyObject* self, PyObject* args, PyObject* kwargs) if (filepathLength < 5) { mvThrowPythonError(mvErrorCode::mvNone, "File path for 'output_frame_buffer(...)' must be of the form 'name.png'."); - return GetPyNone(); + return nullptr; } // TODO: support other formats @@ -2479,7 +2496,7 @@ output_frame_buffer(PyObject* self, PyObject* args, PyObject* kwargs) else { mvThrowPythonError(mvErrorCode::mvNone, "File path for 'output_frame_buffer(...)' must be of the form 'name.png'."); - return GetPyNone(); + return nullptr; } return GetPyNone(); @@ -2503,7 +2520,7 @@ setup_dearpygui(PyObject* self, PyObject* args, PyObject* kwargs) if (GContext->started) { mvThrowPythonError(mvErrorCode::mvNone, "Cannot call \"setup_dearpygui\" while a Dear PyGUI app is already running."); - return GetPyNone(); + return nullptr; } while (!GContext->itemRegistry->containers.empty()) @@ -2680,7 +2697,7 @@ configure_app(PyObject* self, PyObject* args, PyObject* kwargs) { assert(false); mvThrowPythonError(mvErrorCode::mvNone, "Dictionary keywords must be strings"); - return GetPyNone(); + return nullptr; } mvPySafeLockGuard lk(GContext->mutex); @@ -2760,7 +2777,7 @@ get_mouse_pos(PyObject* self, PyObject* args, PyObject* kwargs) b32 local = true; if (!Parse((GetParsers())["get_mouse_pos"], args, kwargs, __FUNCTION__, &local)) - return GetPyNone(); + return nullptr; auto pos = mvVec2(); @@ -2777,7 +2794,7 @@ get_plot_mouse_pos(PyObject* self, PyObject* args, PyObject* kwargs) { if (!Parse((GetParsers())["get_plot_mouse_pos"], args, kwargs, __FUNCTION__)) - return GetPyNone(); + return nullptr; mvVec2 pos = { (f32)GContext->input.mousePlotPos.x, (f32)GContext->input.mousePlotPos.y }; @@ -2789,7 +2806,7 @@ get_drawing_mouse_pos(PyObject* self, PyObject* args, PyObject* kwargs) { if (!Parse((GetParsers())["get_drawing_mouse_pos"], args, kwargs, __FUNCTION__)) - return GetPyNone(); + return nullptr; mvVec2 pos = { (f32)GContext->input.mouseDrawingPos.x, (f32)GContext->input.mouseDrawingPos.y }; @@ -2810,7 +2827,7 @@ is_key_pressed(PyObject* self, PyObject* args, PyObject* kwargs) ImGuiKey key; if (!Parse((GetParsers())["is_key_pressed"], args, kwargs, __FUNCTION__, &key)) - return GetPyNone(); + return nullptr; return ToPyBool(ImGui::IsKeyPressed(key)); } @@ -2821,7 +2838,7 @@ is_key_released(PyObject* self, PyObject* args, PyObject* kwargs) ImGuiKey key; if (!Parse((GetParsers())["is_key_released"], args, kwargs, __FUNCTION__, &key)) - return GetPyNone(); + return nullptr; return ToPyBool(ImGui::IsKeyReleased(key)); } @@ -2832,7 +2849,7 @@ is_key_down(PyObject* self, PyObject* args, PyObject* kwargs) ImGuiKey key; if (!Parse((GetParsers())["is_key_down"], args, kwargs, __FUNCTION__, &key)) - return GetPyNone(); + return nullptr; return ToPyBool(ImGui::IsKeyDown(key)); } @@ -2844,7 +2861,7 @@ is_mouse_button_dragging(PyObject* self, PyObject* args, PyObject* kwargs) f32 threshold; if (!Parse((GetParsers())["is_mouse_button_dragging"], args, kwargs, __FUNCTION__, &button, &threshold)) - return GetPyNone(); + return nullptr; // TODO: Can this be changed? return ToPyBool((f32)ImGui::GetIO().MouseDownDuration[button] >= threshold); @@ -2856,7 +2873,7 @@ is_mouse_button_down(PyObject* self, PyObject* args, PyObject* kwargs) i32 button; if (!Parse((GetParsers())["is_mouse_button_down"], args, kwargs, __FUNCTION__, &button)) - return GetPyNone(); + return nullptr; return ToPyBool(ImGui::IsMouseDown(button)); } @@ -2867,7 +2884,7 @@ is_mouse_button_clicked(PyObject* self, PyObject* args, PyObject* kwargs) i32 button; if (!Parse((GetParsers())["is_mouse_button_clicked"], args, kwargs, __FUNCTION__, &button)) - return GetPyNone(); + return nullptr; return ToPyBool(ImGui::IsMouseClicked(button)); } @@ -2878,7 +2895,7 @@ is_mouse_button_double_clicked(PyObject* self, PyObject* args, PyObject* kwargs) i32 button; if (!Parse((GetParsers())["is_mouse_button_double_clicked"], args, kwargs, __FUNCTION__, &button)) - return GetPyNone(); + return nullptr; return ToPyBool(ImGui::IsMouseDoubleClicked(button)); } @@ -2889,7 +2906,7 @@ is_mouse_button_released(PyObject* self, PyObject* args, PyObject* kwargs) i32 button; if (!Parse((GetParsers())["is_mouse_button_released"], args, kwargs, __FUNCTION__, &button)) - return GetPyNone(); + return nullptr; return ToPyBool(ImGui::IsMouseReleased(button)); } @@ -2904,7 +2921,7 @@ pop_container_stack(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvContainerStackEmpty, "No container to pop."); assert(false); - return GetPyNone(); + return nullptr; } mvAppItem* item = GContext->itemRegistry->containers.top(); @@ -2971,7 +2988,7 @@ push_container_stack(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* itemraw; if (!Parse((GetParsers())["push_container_stack"], args, kwargs, __FUNCTION__, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -2996,10 +3013,10 @@ set_primary_window(PyObject* self, PyObject* args, PyObject* kwargs) i32 value; if (!VerifyRequiredArguments(GetParsers()["set_primary_window"], args)) - return GetPyNone(); + return GetPyNoneOrError(); if (!Parse((GetParsers())["set_primary_window"], args, kwargs, __FUNCTION__, &itemraw, &value)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3008,7 +3025,14 @@ set_primary_window(PyObject* self, PyObject* args, PyObject* kwargs) { mvWindowAppItem* window = GetWindow(*GContext->itemRegistry, item); - if (window) + if (!window) + { + mvThrowPythonError(mvErrorCode::mvItemNotFound, "set_primary_window", + "Item not found: " + std::to_string(item), nullptr); + assert(false); + return nullptr; + } + else { if (window->configData.mainWindow == (bool)value) return GetPyNone(); @@ -3044,12 +3068,6 @@ set_primary_window(PyObject* self, PyObject* args, PyObject* kwargs) } } } - else - { - mvThrowPythonError(mvErrorCode::mvItemNotFound, "set_primary_window", - "Item not found: " + std::to_string(item), nullptr); - assert(false); - } } // reset other windows @@ -3102,7 +3120,7 @@ move_item(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["move_item"], args, kwargs, __FUNCTION__, &itemraw, &parentraw, &beforeraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3124,7 +3142,7 @@ delete_item(PyObject* self, PyObject* args, PyObject* kwargs) i32 slot = -1; if (!Parse((GetParsers())["delete_item"], args, kwargs, __FUNCTION__, &itemraw, &childrenOnly, &slot)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3144,7 +3162,7 @@ does_item_exist(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* itemraw; if (!Parse((GetParsers())["does_item_exist"], args, kwargs, __FUNCTION__, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3162,7 +3180,7 @@ move_item_up(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* itemraw; if (!Parse((GetParsers())["move_item_up"], args, kwargs, __FUNCTION__, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3181,7 +3199,7 @@ move_item_down(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* itemraw; if (!Parse((GetParsers())["move_item_down"], args, kwargs, __FUNCTION__, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3202,7 +3220,7 @@ reorder_items(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["reorder_items"], args, kwargs, __FUNCTION__, &containerraw, &slot, &new_order)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3239,7 +3257,7 @@ unstage(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* itemraw = nullptr; if (!Parse((GetParsers())["unstage"], args, kwargs, __FUNCTION__, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3271,7 +3289,7 @@ unstage(PyObject* self, PyObject* args, PyObject* kwargs) "Stage not found: " + std::to_string(item), nullptr); assert(false); - return GetPyNone(); + return nullptr; } static PyObject* @@ -3281,7 +3299,7 @@ show_item_debug(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* itemraw = nullptr; if (!Parse((GetParsers())["show_item_debug"], args, kwargs, __FUNCTION__, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3297,6 +3315,7 @@ show_item_debug(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "show_item_debug", "Item not found: " + std::to_string(item), nullptr); + return nullptr; } return GetPyNone(); @@ -3419,7 +3438,7 @@ add_alias(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* itemraw; if (!Parse((GetParsers())["add_alias"], args, kwargs, __FUNCTION__, &alias, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3438,7 +3457,7 @@ remove_alias(PyObject* self, PyObject* args, PyObject* kwargs) const char* alias; if (!Parse((GetParsers())["remove_alias"], args, kwargs, __FUNCTION__, &alias)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3455,7 +3474,7 @@ does_alias_exist(PyObject* self, PyObject* args, PyObject* kwargs) const char* alias; if (!Parse((GetParsers())["does_alias_exist"], args, kwargs, __FUNCTION__, &alias)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3471,7 +3490,7 @@ get_alias_id(PyObject* self, PyObject* args, PyObject* kwargs) const char* alias; if (!Parse((GetParsers())["get_alias_id"], args, kwargs, __FUNCTION__, &alias)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3500,7 +3519,7 @@ focus_item(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* itemraw; if (!Parse((GetParsers())["focus_item"], args, kwargs, __FUNCTION__, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3529,8 +3548,11 @@ focus_item(PyObject* self, PyObject* args, PyObject* kwargs) parent->info.focusNextFrame = true; } else + { mvThrowPythonError(mvErrorCode::mvItemNotFound, "focus_item", "Item not found: " + std::to_string(item), nullptr); + return nullptr; + } return GetPyNone(); } @@ -3573,82 +3595,76 @@ get_item_info(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* itemraw; if (!Parse((GetParsers())["get_item_info"], args, kwargs, __FUNCTION__, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); mvUUID item = GetIDFromPyObject(itemraw); mvAppItem* appitem = GetItem((*GContext->itemRegistry), item); - PyObject* pdict = PyDict_New(); - if (appitem) + if (!appitem) { + mvThrowPythonError(mvErrorCode::mvItemNotFound, "get_item_info", + "Item not found: " + std::to_string(item), nullptr); + return nullptr; + } + + PyObject* pdict = PyDict_New(); - std::string parserCommand = GetEntityCommand(appitem->type); + std::string parserCommand = GetEntityCommand(appitem->type); - auto children = GetItemChildren(*GContext->itemRegistry, appitem->uuid); - if (children.empty()) - PyDict_SetItemString(pdict, "children", mvPyObject(GetPyNone())); - else + auto children = GetItemChildren(*GContext->itemRegistry, appitem->uuid); + if (children.empty()) + PyDict_SetItemString(pdict, "children", mvPyObject(GetPyNone())); + else + { + PyObject* pyChildren = PyDict_New(); + i32 i = 0; + for (const auto& slot : children) { - PyObject* pyChildren = PyDict_New(); - i32 i = 0; - for (const auto& slot : children) - { - PyDict_SetItem(pyChildren, ToPyInt(i), mvPyObject(ToPyList(slot))); - i++; - } - PyDict_SetItemString(pdict, "children", mvPyObject(pyChildren)); + PyDict_SetItem(pyChildren, ToPyInt(i), mvPyObject(ToPyList(slot))); + i++; } + PyDict_SetItemString(pdict, "children", mvPyObject(pyChildren)); + } - PyDict_SetItemString(pdict, "type", mvPyObject(ToPyString(DearPyGui::GetEntityTypeString(appitem->type)))); - PyDict_SetItemString(pdict, "target", mvPyObject(ToPyInt(DearPyGui::GetEntityTargetSlot(appitem->type)))); - - if (appitem->info.parentPtr) - PyDict_SetItemString(pdict, "parent", mvPyObject(ToPyUUID(appitem->info.parentPtr->uuid))); - else - PyDict_SetItemString(pdict, "parent", mvPyObject(GetPyNone())); - - if (appitem->theme) - PyDict_SetItemString(pdict, "theme", mvPyObject(ToPyUUID(appitem->theme->uuid))); - else - PyDict_SetItemString(pdict, "theme", mvPyObject(GetPyNone())); - - if (appitem->handlerRegistry) - PyDict_SetItemString(pdict, "handlers", mvPyObject(ToPyUUID(appitem->handlerRegistry->uuid))); - else - PyDict_SetItemString(pdict, "handlers", mvPyObject(GetPyNone())); - - if (appitem->font) - PyDict_SetItemString(pdict, "font", mvPyObject(ToPyUUID(appitem->font->uuid))); - else - PyDict_SetItemString(pdict, "font", mvPyObject(GetPyNone())); + PyDict_SetItemString(pdict, "type", mvPyObject(ToPyString(DearPyGui::GetEntityTypeString(appitem->type)))); + PyDict_SetItemString(pdict, "target", mvPyObject(ToPyInt(DearPyGui::GetEntityTargetSlot(appitem->type)))); - if (DearPyGui::GetEntityDesciptionFlags(appitem->type) & MV_ITEM_DESC_CONTAINER) - PyDict_SetItemString(pdict, "container", mvPyObject(ToPyBool(true))); - else - PyDict_SetItemString(pdict, "container", mvPyObject(ToPyBool(false))); + if (appitem->info.parentPtr) + PyDict_SetItemString(pdict, "parent", mvPyObject(ToPyUUID(appitem->info.parentPtr->uuid))); + else + PyDict_SetItemString(pdict, "parent", mvPyObject(GetPyNone())); - i32 applicableState = DearPyGui::GetApplicableState(appitem->type); - PyDict_SetItemString(pdict, "hover_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_HOVER))); - PyDict_SetItemString(pdict, "active_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_ACTIVE))); - PyDict_SetItemString(pdict, "focus_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_FOCUSED))); - PyDict_SetItemString(pdict, "clicked_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_CLICKED))); - PyDict_SetItemString(pdict, "visible_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_VISIBLE))); - PyDict_SetItemString(pdict, "edited_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_EDITED))); - PyDict_SetItemString(pdict, "activated_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_ACTIVATED))); - PyDict_SetItemString(pdict, "deactivated_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_DEACTIVATED))); - PyDict_SetItemString(pdict, "deactivatedae_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_DEACTIVATEDAE))); - PyDict_SetItemString(pdict, "toggled_open_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_TOGGLED_OPEN))); - PyDict_SetItemString(pdict, "resized_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_RECT_SIZE))); - PyDict_SetItemString(pdict, "scroll_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_SCROLL))); + if (appitem->theme) + PyDict_SetItemString(pdict, "theme", mvPyObject(ToPyUUID(appitem->theme->uuid))); + else + PyDict_SetItemString(pdict, "theme", mvPyObject(GetPyNone())); - } + if (appitem->font) + PyDict_SetItemString(pdict, "font", mvPyObject(ToPyUUID(appitem->font->uuid))); + else + PyDict_SetItemString(pdict, "font", mvPyObject(GetPyNone())); + if (DearPyGui::GetEntityDesciptionFlags(appitem->type) & MV_ITEM_DESC_CONTAINER) + PyDict_SetItemString(pdict, "container", mvPyObject(ToPyBool(true))); else - mvThrowPythonError(mvErrorCode::mvItemNotFound, "get_item_info", - "Item not found: " + std::to_string(item), nullptr); + PyDict_SetItemString(pdict, "container", mvPyObject(ToPyBool(false))); + + i32 applicableState = DearPyGui::GetApplicableState(appitem->type); + PyDict_SetItemString(pdict, "hover_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_HOVER))); + PyDict_SetItemString(pdict, "active_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_ACTIVE))); + PyDict_SetItemString(pdict, "focus_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_FOCUSED))); + PyDict_SetItemString(pdict, "clicked_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_CLICKED))); + PyDict_SetItemString(pdict, "visible_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_VISIBLE))); + PyDict_SetItemString(pdict, "edited_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_EDITED))); + PyDict_SetItemString(pdict, "activated_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_ACTIVATED))); + PyDict_SetItemString(pdict, "deactivated_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_DEACTIVATED))); + PyDict_SetItemString(pdict, "deactivatedae_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_DEACTIVATEDAE))); + PyDict_SetItemString(pdict, "toggled_open_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_TOGGLED_OPEN))); + PyDict_SetItemString(pdict, "resized_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_RECT_SIZE))); + PyDict_SetItemString(pdict, "scroll_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_SCROLL))); return pdict; } @@ -3659,61 +3675,62 @@ get_item_configuration(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* itemraw; if (!Parse((GetParsers())["get_item_configuration"], args, kwargs, __FUNCTION__, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); mvUUID item = GetIDFromPyObject(itemraw); mvAppItem* appitem = GetItem((*GContext->itemRegistry), item); - PyObject* pdict = PyDict_New(); - - if (appitem) + if (!appitem) { - // config py objects - mvPyObject py_filter_key = ToPyString(appitem->config.filter); - mvPyObject py_payload_type = ToPyString(appitem->config.payloadType); - mvPyObject py_label = ToPyString(appitem->config.specifiedLabel); - mvPyObject py_use_internal_label = ToPyBool(appitem->config.useInternalLabel); - mvPyObject py_source = ToPyUUID(appitem->config.source); - mvPyObject py_show = ToPyBool(appitem->config.show); - mvPyObject py_enabled = ToPyBool(appitem->config.enabled); - mvPyObject py_tracked = ToPyBool(appitem->config.tracked); - mvPyObject py_width = ToPyInt(appitem->config.width); - mvPyObject py_track_offset = ToPyFloat(appitem->config.trackOffset); - mvPyObject py_height = ToPyInt(appitem->config.height); - mvPyObject py_indent = ToPyInt((i32)appitem->config.indent); - - PyDict_SetItemString(pdict, "filter_key", py_filter_key); - PyDict_SetItemString(pdict, "payload_type", py_payload_type); - PyDict_SetItemString(pdict, "label", py_label); - PyDict_SetItemString(pdict, "use_internal_label", py_use_internal_label); - PyDict_SetItemString(pdict, "source", py_source); - PyDict_SetItemString(pdict, "show", py_show); - PyDict_SetItemString(pdict, "enabled", py_enabled); - PyDict_SetItemString(pdict, "tracked", py_tracked); - PyDict_SetItemString(pdict, "width", py_width); - PyDict_SetItemString(pdict, "track_offset", py_track_offset); - PyDict_SetItemString(pdict, "height", py_height); - PyDict_SetItemString(pdict, "indent", py_indent); - - PyObject* callback = appitem->config.callback; - PyDict_SetItemString(pdict, "callback", callback? callback : Py_None); - - PyObject* dropCallback = appitem->config.dropCallback; - PyDict_SetItemString(pdict, "drop_callback", dropCallback? dropCallback : Py_None); - - PyObject* dragCallback = appitem->config.dragCallback; - PyDict_SetItemString(pdict, "drag_callback", dragCallback? dragCallback : Py_None); - - PyObject* user_data = *(appitem->config.user_data); - PyDict_SetItemString(pdict, "user_data", user_data? user_data : Py_None); - - appitem->getSpecificConfiguration(pdict); - } - else mvThrowPythonError(mvErrorCode::mvItemNotFound, "get_item_configuration", "Item not found: " + std::to_string(item), nullptr); + return nullptr; + } + + PyObject* pdict = PyDict_New(); + + // config py objects + mvPyObject py_filter_key = ToPyString(appitem->config.filter); + mvPyObject py_payload_type = ToPyString(appitem->config.payloadType); + mvPyObject py_label = ToPyString(appitem->config.specifiedLabel); + mvPyObject py_use_internal_label = ToPyBool(appitem->config.useInternalLabel); + mvPyObject py_source = ToPyUUID(appitem->config.source); + mvPyObject py_show = ToPyBool(appitem->config.show); + mvPyObject py_enabled = ToPyBool(appitem->config.enabled); + mvPyObject py_tracked = ToPyBool(appitem->config.tracked); + mvPyObject py_width = ToPyInt(appitem->config.width); + mvPyObject py_track_offset = ToPyFloat(appitem->config.trackOffset); + mvPyObject py_height = ToPyInt(appitem->config.height); + mvPyObject py_indent = ToPyInt((i32)appitem->config.indent); + + PyDict_SetItemString(pdict, "filter_key", py_filter_key); + PyDict_SetItemString(pdict, "payload_type", py_payload_type); + PyDict_SetItemString(pdict, "label", py_label); + PyDict_SetItemString(pdict, "use_internal_label", py_use_internal_label); + PyDict_SetItemString(pdict, "source", py_source); + PyDict_SetItemString(pdict, "show", py_show); + PyDict_SetItemString(pdict, "enabled", py_enabled); + PyDict_SetItemString(pdict, "tracked", py_tracked); + PyDict_SetItemString(pdict, "width", py_width); + PyDict_SetItemString(pdict, "track_offset", py_track_offset); + PyDict_SetItemString(pdict, "height", py_height); + PyDict_SetItemString(pdict, "indent", py_indent); + + PyObject* callback = appitem->config.callback; + PyDict_SetItemString(pdict, "callback", callback? callback : Py_None); + + PyObject* dropCallback = appitem->config.dropCallback; + PyDict_SetItemString(pdict, "drop_callback", dropCallback? dropCallback : Py_None); + + PyObject* dragCallback = appitem->config.dragCallback; + PyDict_SetItemString(pdict, "drag_callback", dragCallback? dragCallback : Py_None); + + PyObject* user_data = *(appitem->config.user_data); + PyDict_SetItemString(pdict, "user_data", user_data? user_data : Py_None); + + appitem->getSpecificConfiguration(pdict); return pdict; } @@ -3727,7 +3744,7 @@ set_item_children(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["set_item_children"], args, kwargs, __FUNCTION__, &itemraw, &sourceraw, &slot)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3755,7 +3772,7 @@ set_item_children(PyObject* self, PyObject* args, PyObject* kwargs) mvThrowPythonError(mvErrorCode::mvItemNotFound, "set_item_children", "Stage item not found: " + std::to_string(item), nullptr); assert(false); - return GetPyNone(); + return nullptr; } @@ -3782,8 +3799,11 @@ set_item_children(PyObject* self, PyObject* args, PyObject* kwargs) } } else + { mvThrowPythonError(mvErrorCode::mvItemNotFound, "set_item_children", "Item not found: " + std::to_string(item), nullptr); + return nullptr; + } DeleteItem(*GContext->itemRegistry, source); @@ -3798,7 +3818,7 @@ bind_item_font(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["bind_item_font"], args, kwargs, __FUNCTION__, &itemraw, &fontraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3818,6 +3838,7 @@ bind_item_font(PyObject* self, PyObject* args, PyObject* kwargs) if (appfont) { appitem->font = appfont; + return GetPyNone(); } else { @@ -3829,7 +3850,7 @@ bind_item_font(PyObject* self, PyObject* args, PyObject* kwargs) mvThrowPythonError(mvErrorCode::mvItemNotFound, "bind_item_font", "Item not found: " + std::to_string(item), nullptr); - return GetPyNone(); + return nullptr; } static PyObject* @@ -3840,7 +3861,7 @@ bind_item_theme(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["bind_item_theme"], args, kwargs, __FUNCTION__, &itemraw, &themeraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3860,13 +3881,14 @@ bind_item_theme(PyObject* self, PyObject* args, PyObject* kwargs) if (apptheme) { - if (apptheme->type != mvAppItemType::mvTheme) + if (apptheme->type == mvAppItemType::mvTheme) { + appitem->theme = *(std::shared_ptr*)(&apptheme); + return GetPyNone(); + } + else mvThrowPythonError(mvErrorCode::mvIncompatibleType, "bind_item_theme", "Item not a theme: " + std::to_string(theme), nullptr); - } - appitem->theme = *(std::shared_ptr*)(&apptheme); - return GetPyNone(); } else mvThrowPythonError(mvErrorCode::mvItemNotFound, "bind_item_theme", @@ -3876,7 +3898,7 @@ bind_item_theme(PyObject* self, PyObject* args, PyObject* kwargs) mvThrowPythonError(mvErrorCode::mvItemNotFound, "bind_item_theme", "Item not found: " + std::to_string(item), nullptr); - return GetPyNone(); + return nullptr; } static PyObject* @@ -3887,7 +3909,7 @@ bind_item_handler_registry(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["bind_item_handler_registry"], args, kwargs, __FUNCTION__, &itemraw, ®raw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3903,18 +3925,19 @@ bind_item_handler_registry(PyObject* self, PyObject* args, PyObject* kwargs) return GetPyNone(); } - auto apptheme = GetRefItem(*GContext->itemRegistry, reg); + auto handler_registry = GetRefItem(*GContext->itemRegistry, reg); - if (apptheme) + if (handler_registry) { - if (apptheme->type != mvAppItemType::mvItemHandlerRegistry) + if (handler_registry->type == mvAppItemType::mvItemHandlerRegistry) { + appitem->handlerRegistry = *(std::shared_ptr*)(&handler_registry); + appitem->handlerRegistry->onBind(appitem); + return GetPyNoneOrError(); + } + else mvThrowPythonError(mvErrorCode::mvIncompatibleType, "bind_item_handler_registry", "Item not handler registry: " + std::to_string(reg), nullptr); - } - appitem->handlerRegistry = *(std::shared_ptr*)(&apptheme); - appitem->handlerRegistry->onBind(appitem); - return GetPyNone(); } else mvThrowPythonError(mvErrorCode::mvItemNotFound, "bind_item_handler_registry", @@ -3924,7 +3947,7 @@ bind_item_handler_registry(PyObject* self, PyObject* args, PyObject* kwargs) mvThrowPythonError(mvErrorCode::mvItemNotFound, "bind_item_handler_registry", "Item not found: " + std::to_string(item), nullptr); - return GetPyNone(); + return nullptr; } static PyObject* @@ -3934,7 +3957,7 @@ reset_pos(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["reset_pos"], args, kwargs, __FUNCTION__, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -3944,8 +3967,11 @@ reset_pos(PyObject* self, PyObject* args, PyObject* kwargs) if (appitem) appitem->info.dirtyPos = false; else + { mvThrowPythonError(mvErrorCode::mvItemNotFound, "reset_pos", "Item not found: " + std::to_string(item), nullptr); + return nullptr; + } return GetPyNone(); } @@ -3956,20 +3982,22 @@ get_item_state(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* itemraw; if (!Parse((GetParsers())["get_item_state"], args, kwargs, __FUNCTION__, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); mvUUID item = GetIDFromPyObject(itemraw); mvAppItem* appitem = GetItem((*GContext->itemRegistry), item); - PyObject* pdict = PyDict_New(); - - if (appitem) - FillAppItemState(pdict, appitem->state, DearPyGui::GetApplicableState(appitem->type)); - else + if (!appitem) + { mvThrowPythonError(mvErrorCode::mvItemNotFound, "get_item_state", "Item not found: " + std::to_string(item), nullptr); + return nullptr; + } + + PyObject* pdict = PyDict_New(); + FillAppItemState(pdict, appitem->state, DearPyGui::GetApplicableState(appitem->type)); return pdict; } @@ -4003,8 +4031,11 @@ configure_item(PyObject* self, PyObject* args, PyObject* kwargs) appitem->handleKeywordArgs(kwargs, GetEntityCommand(appitem->type)); } else + { mvThrowPythonError(mvErrorCode::mvItemNotFound, "configure_item", "Item not found: " + std::to_string(item), nullptr); + return nullptr; + } return GetPyNone(); } @@ -4015,7 +4046,7 @@ get_value(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* nameraw; if (!Parse((GetParsers())["get_value"], args, kwargs, __FUNCTION__, &nameraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -4033,7 +4064,7 @@ get_values(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* items; if (!Parse((GetParsers())["get_values"], args, kwargs, __FUNCTION__, &items)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -4047,6 +4078,9 @@ get_values(PyObject* self, PyObject* args, PyObject* kwargs) PyList_SetItem(pyvalues, i, item->getPyValue()); else { + // TODO: decide whether we want to raise an exception or return None's. + // These two variants are mutually exclusive. If we raise an exception, + // we must return nullptr, not pyvalues. mvThrowPythonError(mvErrorCode::mvItemNotFound, "get_values", "Item not found: " + std::to_string(aitems[i]), nullptr); PyList_SetItem(pyvalues, i, GetPyNone()); @@ -4063,24 +4097,22 @@ set_value(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* value; if (!Parse((GetParsers())["set_value"], args, kwargs, __FUNCTION__, &nameraw, &value)) - return GetPyNone(); - - if (value) - Py_XINCREF(value); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); mvUUID name = GetIDFromPyObject(nameraw); mvAppItem* item = GetItem(*GContext->itemRegistry, name); - if (item) - item->setPyValue(value); - else + if (!item) { mvThrowPythonError(mvErrorCode::mvItemNotFound, "set_value", "Item not found: " + std::to_string(name), nullptr); + return nullptr; } + Py_XINCREF(value); + item->setPyValue(value); Py_XDECREF(value); return GetPyNone(); @@ -4094,7 +4126,7 @@ set_item_alias(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["set_item_alias"], args, kwargs, __FUNCTION__, &itemraw, &alias)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -4112,7 +4144,7 @@ get_item_alias(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["get_item_alias"], args, kwargs, __FUNCTION__, &itemraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -4131,7 +4163,7 @@ capture_next_item(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["capture_next_item"], args, kwargs, __FUNCTION__, &callable, &user_data)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -4201,7 +4233,7 @@ set_clipboard_text(PyObject* self, PyObject* args, PyObject* kwargs) if (!Parse((GetParsers())["set_clipboard_text"], args, kwargs, __FUNCTION__, &text)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); diff --git a/src/mvPyUtils.cpp b/src/mvPyUtils.cpp index 782bbe684..e44406c91 100644 --- a/src/mvPyUtils.cpp +++ b/src/mvPyUtils.cpp @@ -457,6 +457,12 @@ GetPyNone() Py_RETURN_NONE; } +PyObject* +GetPyNoneOrError() +{ + return (PyErr_Occurred() != nullptr)? nullptr : GetPyNone(); +} + PyObject* ToPyString(const std::string& value) { diff --git a/src/mvPyUtils.h b/src/mvPyUtils.h index 252f71f1d..573f56547 100644 --- a/src/mvPyUtils.h +++ b/src/mvPyUtils.h @@ -136,6 +136,7 @@ bool isPyObject_Any (PyObject* obj); // conversion to python PyObject* GetPyNone (); +PyObject* GetPyNoneOrError (); PyObject* ToPyUUID (mvAppItem* item); PyObject* ToPyUUID (mvUUID value); PyObject* ToPyUUID (mvUUID uuid, const std::string& alias); From b7f747da7f86f508f63aae876e0d920d44833ed1 Mon Sep 17 00:00:00 2001 From: Vladimir Ein Date: Wed, 24 Dec 2025 23:27:30 +0500 Subject: [PATCH 39/58] fix: make sure new API functions return null on an exception #2097 --- src/dearpygui_commands.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/dearpygui_commands.h b/src/dearpygui_commands.h index da838cc46..95d37cbf2 100644 --- a/src/dearpygui_commands.h +++ b/src/dearpygui_commands.h @@ -1032,7 +1032,7 @@ set_axis_limits_constraints(PyObject* self, PyObject* args, PyObject* kwargs) auto tag = "set_axis_limits_constraints"; if (!Parse((GetParsers())[tag], args, kwargs, __FUNCTION__, &axisraw, &vmin, &vmax)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1043,14 +1043,14 @@ set_axis_limits_constraints(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, tag, "Item not found: " + std::to_string(axis), nullptr); - return GetPyNone(); + return nullptr; } if (aplot->type != mvAppItemType::mvPlotAxis) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, tag, "Incompatible type. Expected types include: mvPlotAxis", aplot); - return GetPyNone(); + return nullptr; } mvPlotAxis* graph = static_cast(aplot); @@ -1066,7 +1066,7 @@ reset_axis_limits_constraints(PyObject* self, PyObject* args, PyObject* kwargs) auto tag = "reset_axis_limits_constraints"; if (!Parse((GetParsers())[tag], args, kwargs, __FUNCTION__, &axisraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1077,14 +1077,14 @@ reset_axis_limits_constraints(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, tag, "Item not found: " + std::to_string(axis), nullptr); - return GetPyNone(); + return nullptr; } if (aplot->type != mvAppItemType::mvPlotAxis) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, tag, "Incompatible type. Expected types include: mvPlotAxis", aplot); - return GetPyNone(); + return nullptr; } mvPlotAxis* graph = static_cast(aplot); @@ -1101,7 +1101,7 @@ set_axis_zoom_constraints(PyObject* self, PyObject* args, PyObject* kwargs) auto tag = "set_axis_zoom_constraints"; if (!Parse((GetParsers())[tag], args, kwargs, __FUNCTION__, &axisraw, &vmin, &vmax)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1112,14 +1112,14 @@ set_axis_zoom_constraints(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, tag, "Item not found: " + std::to_string(axis), nullptr); - return GetPyNone(); + return nullptr; } if (aplot->type != mvAppItemType::mvPlotAxis) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, tag, "Incompatible type. Expected types include: mvPlotAxis", aplot); - return GetPyNone(); + return nullptr; } mvPlotAxis* graph = static_cast(aplot); @@ -1136,7 +1136,7 @@ reset_axis_zoom_constraints(PyObject* self, PyObject* args, PyObject* kwargs) auto tag = "reset_axis_zoom_constraints"; if (!Parse((GetParsers())[tag], args, kwargs, __FUNCTION__, &axisraw)) - return GetPyNone(); + return nullptr; mvPySafeLockGuard lk(GContext->mutex); @@ -1147,14 +1147,14 @@ reset_axis_zoom_constraints(PyObject* self, PyObject* args, PyObject* kwargs) { mvThrowPythonError(mvErrorCode::mvItemNotFound, tag, "Item not found: " + std::to_string(axis), nullptr); - return GetPyNone(); + return nullptr; } if (aplot->type != mvAppItemType::mvPlotAxis) { mvThrowPythonError(mvErrorCode::mvIncompatibleType, tag, "Incompatible type. Expected types include: mvPlotAxis", aplot); - return GetPyNone(); + return nullptr; } mvPlotAxis* graph = static_cast(aplot); From 99058f353e2d19f244bcdf2ef60448dcebbe6ac0 Mon Sep 17 00:00:00 2001 From: Vladimir Ein Date: Sun, 4 Jan 2026 20:39:07 +0500 Subject: [PATCH 40/58] fix (mvLoadingIndicator): Corrected size calculations for the ring-style indicator. Old indicator rendering is kept at style=1 for compatibility; new corrected rendering can be selected with style=mvLoadInd_Ring. Also, colors are taken from Button/ButtonHovered styles, as was intended in the original design. Also, the indicator should not receive navigation focus (since it's not actionable anyway). --- dearpygui/_dearpygui.pyi | 2 + dearpygui/_dearpygui_RTD.py | 16 ++++---- dearpygui/dearpygui.py | 18 +++++---- src/dearpygui.cpp | 3 ++ src/mvAppItem.cpp | 14 +++---- src/mvLoadingIndicator.cpp | 39 +++++++++++++++--- src/mvLoadingIndicator.h | 13 ++++-- src/mvLoadingIndicatorCustom.cpp | 68 ++++++++++++++++++++++++++++---- src/mvLoadingIndicatorCustom.h | 4 +- 9 files changed, 138 insertions(+), 39 deletions(-) diff --git a/dearpygui/_dearpygui.pyi b/dearpygui/_dearpygui.pyi index 3ffbd5356..0a2550da3 100644 --- a/dearpygui/_dearpygui.pyi +++ b/dearpygui/_dearpygui.pyi @@ -1445,6 +1445,8 @@ mvScrollDirection_XAxis=0 mvScrollDirection_YAxis=0 mvScrollDirection_Horizontal=0 mvScrollDirection_Vertical=0 +mvLoadInd_DottedCircle=0 +mvLoadInd_Ring=0 mvPlatform_Windows=0 mvPlatform_Apple=0 mvPlatform_Linux=0 diff --git a/dearpygui/_dearpygui_RTD.py b/dearpygui/_dearpygui_RTD.py index abb68f6a9..0b3f7f0ba 100644 --- a/dearpygui/_dearpygui_RTD.py +++ b/dearpygui/_dearpygui_RTD.py @@ -5222,13 +5222,13 @@ def add_loading_indicator(**kwargs): drop_callback (Callable, optional): Registers a drop callback for drag and drop. show (bool, optional): Attempt to render widget. pos (Union[List[int], Tuple[int, ...]], optional): Places the item relative to window coordinates, [0,0] is top left. - style (int, optional): 0 is rotating dots style, 1 is rotating bar style. - circle_count (int, optional): Number of dots show if dots or size of circle if circle. - speed (float, optional): Speed the anamation will rotate. - radius (float, optional): Radius size of the loading indicator. - thickness (float, optional): Thickness of the circles or line. - color (Union[List[int], Tuple[int, ...]], optional): Color of the growing center circle. - secondary_color (Union[List[int], Tuple[int, ...]], optional): Background of the dots in dot mode. + style (int, optional): mvLoadInd_DottedCircle is rotating dots style, mvLoadInd_Ring is rotating bar style. + circle_count (int, optional): DottedCircle style: number of dots to show. + speed (float, optional): Speed with which the animation will rotate. + radius (float, optional): Scale factor for the loading indicator radius. The size of the indicator is determined by font size and this scale factor. + thickness (float, optional): Ring style: scale factor of line thickness; thickness=1 corresponds to line width being 1/8 of the ring diameter. + color (Union[List[int], Tuple[int, ...]], optional): Main color of the indicator. If omitted, the color for mvThemeCol_Button will be used. + secondary_color (Union[List[int], Tuple[int, ...]], optional): DottedCircle style: color of 'inactive' dots. If omitted, the color for mvThemeCol_ButtonHovered will be used. id (Union[int, str], optional): (deprecated) Returns: Union[int, str] @@ -9016,6 +9016,8 @@ def unstage(item): mvScrollDirection_YAxis=internal_dpg.mvScrollDirection_YAxis mvScrollDirection_Horizontal=internal_dpg.mvScrollDirection_Horizontal mvScrollDirection_Vertical=internal_dpg.mvScrollDirection_Vertical +mvLoadInd_DottedCircle=internal_dpg.mvLoadInd_DottedCircle +mvLoadInd_Ring=internal_dpg.mvLoadInd_Ring mvPlatform_Windows=internal_dpg.mvPlatform_Windows mvPlatform_Apple=internal_dpg.mvPlatform_Apple mvPlatform_Linux=internal_dpg.mvPlatform_Linux diff --git a/dearpygui/dearpygui.py b/dearpygui/dearpygui.py index aa4c2d846..fbde8f8ce 100644 --- a/dearpygui/dearpygui.py +++ b/dearpygui/dearpygui.py @@ -5792,7 +5792,7 @@ def add_listbox(items : Union[List[str], Tuple[str, ...]] =(), *, label: str =No return internal_dpg.add_listbox(items, label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, width=width, indent=indent, parent=parent, before=before, source=source, payload_type=payload_type, callback=callback, drag_callback=drag_callback, drop_callback=drop_callback, show=show, enabled=enabled, pos=pos, filter_key=filter_key, tracked=tracked, track_offset=track_offset, default_value=default_value, num_items=num_items, **kwargs) -def add_loading_indicator(*, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, width: int =0, height: int =0, indent: int =-1, parent: Union[int, str] =0, before: Union[int, str] =0, payload_type: str ='$$DPG_PAYLOAD', drop_callback: Callable =None, show: bool =True, pos: Union[List[int], Tuple[int, ...]] =[], style: int =0, circle_count: int =8, speed: float =1.0, radius: float =3.0, thickness: float =1.0, color: Union[List[int], Tuple[int, ...]] =(51, 51, 55, 255), secondary_color: Union[List[int], Tuple[int, ...]] =(29, 151, 236, 103), **kwargs) -> Union[int, str]: +def add_loading_indicator(*, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, width: int =0, height: int =0, indent: int =-1, parent: Union[int, str] =0, before: Union[int, str] =0, payload_type: str ='$$DPG_PAYLOAD', drop_callback: Callable =None, show: bool =True, pos: Union[List[int], Tuple[int, ...]] =[], style: int =0, circle_count: int =8, speed: float =1.0, radius: float =3.0, thickness: float =1.0, color: Union[List[int], Tuple[int, ...]] =None, secondary_color: Union[List[int], Tuple[int, ...]] =None, **kwargs) -> Union[int, str]: """ Adds a rotating animated loading symbol. Args: @@ -5809,13 +5809,13 @@ def add_loading_indicator(*, label: str =None, user_data: Any =None, use_interna drop_callback (Callable, optional): Registers a drop callback for drag and drop. show (bool, optional): Attempt to render widget. pos (Union[List[int], Tuple[int, ...]], optional): Places the item relative to window coordinates, [0,0] is top left. - style (int, optional): 0 is rotating dots style, 1 is rotating bar style. - circle_count (int, optional): Number of dots show if dots or size of circle if circle. - speed (float, optional): Speed the anamation will rotate. - radius (float, optional): Radius size of the loading indicator. - thickness (float, optional): Thickness of the circles or line. - color (Union[List[int], Tuple[int, ...]], optional): Color of the growing center circle. - secondary_color (Union[List[int], Tuple[int, ...]], optional): Background of the dots in dot mode. + style (int, optional): mvLoadInd_DottedCircle is rotating dots style, mvLoadInd_Ring is rotating bar style. + circle_count (int, optional): DottedCircle style: number of dots to show. + speed (float, optional): Speed with which the animation will rotate. + radius (float, optional): Scale factor for the loading indicator radius. The size of the indicator is determined by font size and this scale factor. + thickness (float, optional): Ring style: scale factor of line thickness; thickness=1 corresponds to line width being 1/8 of the ring diameter. + color (Union[List[int], Tuple[int, ...]], optional): Main color of the indicator. If omitted, the color for mvThemeCol_Button will be used. + secondary_color (Union[List[int], Tuple[int, ...]], optional): DottedCircle style: color of 'inactive' dots. If omitted, the color for mvThemeCol_ButtonHovered will be used. id (Union[int, str], optional): (deprecated) Returns: Union[int, str] @@ -9999,6 +9999,8 @@ def unstage(item : Union[int, str], **kwargs) -> None: mvScrollDirection_YAxis=internal_dpg.mvScrollDirection_YAxis mvScrollDirection_Horizontal=internal_dpg.mvScrollDirection_Horizontal mvScrollDirection_Vertical=internal_dpg.mvScrollDirection_Vertical +mvLoadInd_DottedCircle=internal_dpg.mvLoadInd_DottedCircle +mvLoadInd_Ring=internal_dpg.mvLoadInd_Ring mvPlatform_Windows=internal_dpg.mvPlatform_Windows mvPlatform_Apple=internal_dpg.mvPlatform_Apple mvPlatform_Linux=internal_dpg.mvPlatform_Linux diff --git a/src/dearpygui.cpp b/src/dearpygui.cpp index d26509634..3a878f7a3 100644 --- a/src/dearpygui.cpp +++ b/src/dearpygui.cpp @@ -84,6 +84,9 @@ GetModuleConstants() ModuleConstants.push_back({"mvScrollDirection_Horizontal", mvScrollDirection_Horizontal }); ModuleConstants.push_back({"mvScrollDirection_Vertical", mvScrollDirection_Vertical }); + ModuleConstants.push_back({"mvLoadInd_DottedCircle", mvLoadingIndicator::Style_DottedCircle }); + ModuleConstants.push_back({"mvLoadInd_Ring", mvLoadingIndicator::Style_Ring }); + ModuleConstants.push_back({"mvPlatform_Windows", 0L }); ModuleConstants.push_back({"mvPlatform_Apple", 1L }); ModuleConstants.push_back({"mvPlatform_Linux", 2L }); diff --git a/src/mvAppItem.cpp b/src/mvAppItem.cpp index 4815f8797..0cb2c948c 100644 --- a/src/mvAppItem.cpp +++ b/src/mvAppItem.cpp @@ -4542,13 +4542,13 @@ DearPyGui::GetEntityParser(mvAppItemType type) MV_PARSER_ARG_POS) ); - args.push_back({ mvPyDataType::Integer, "style", mvArgType::KEYWORD_ARG, "0", "0 is rotating dots style, 1 is rotating bar style." }); - args.push_back({ mvPyDataType::Integer, "circle_count", mvArgType::KEYWORD_ARG, "8", "Number of dots show if dots or size of circle if circle." }); - args.push_back({ mvPyDataType::Float, "speed", mvArgType::KEYWORD_ARG, "1.0", "Speed the anamation will rotate." }); - args.push_back({ mvPyDataType::Float, "radius", mvArgType::KEYWORD_ARG, "3.0", "Radius size of the loading indicator." }); - args.push_back({ mvPyDataType::Float, "thickness", mvArgType::KEYWORD_ARG, "1.0", "Thickness of the circles or line." }); - args.push_back({ mvPyDataType::IntList, "color", mvArgType::KEYWORD_ARG, "(51, 51, 55, 255)", "Color of the growing center circle." }); - args.push_back({ mvPyDataType::IntList, "secondary_color", mvArgType::KEYWORD_ARG, "(29, 151, 236, 103)", "Background of the dots in dot mode." }); + args.push_back({ mvPyDataType::Integer, "style", mvArgType::KEYWORD_ARG, "0", "mvLoadInd_DottedCircle is rotating dots style, mvLoadInd_Ring is rotating bar style." }); + args.push_back({ mvPyDataType::Integer, "circle_count", mvArgType::KEYWORD_ARG, "8", "DottedCircle style: number of dots to show." }); + args.push_back({ mvPyDataType::Float, "speed", mvArgType::KEYWORD_ARG, "1.0", "Speed with which the animation will rotate." }); + args.push_back({ mvPyDataType::Float, "radius", mvArgType::KEYWORD_ARG, "3.0", "Scale factor for the loading indicator radius. The size of the indicator is determined by font size and this scale factor." }); + args.push_back({ mvPyDataType::Float, "thickness", mvArgType::KEYWORD_ARG, "1.0", "Ring style: scale factor of line thickness; thickness=1 corresponds to line width being 1/8 of the ring diameter." }); + args.push_back({ mvPyDataType::IntList, "color", mvArgType::KEYWORD_ARG, "None", "Main color of the indicator. If omitted, the color for mvThemeCol_Button will be used." }); + args.push_back({ mvPyDataType::IntList, "secondary_color", mvArgType::KEYWORD_ARG, "None", "DottedCircle style: color of 'inactive' dots. If omitted, the color for mvThemeCol_ButtonHovered will be used." }); setup.about = "Adds a rotating animated loading symbol."; break; diff --git a/src/mvLoadingIndicator.cpp b/src/mvLoadingIndicator.cpp index 173459152..4055602ec 100644 --- a/src/mvLoadingIndicator.cpp +++ b/src/mvLoadingIndicator.cpp @@ -57,10 +57,33 @@ void mvLoadingIndicator::draw(ImDrawList* drawlist, float x, float y) { ScopedID id(uuid); - if (_style == 0) + switch (_style) + { + case Style_OldDottedCircle: LoadingIndicatorCircle(config.specifiedLabel.c_str(), _radius, _mainColor, _optionalColor, _circleCount, _speed); - else + break; + case Style_OldRing: LoadingIndicatorCircle2(config.specifiedLabel.c_str(), _radius, _thickness, _mainColor); + break; + case Style_DottedCircle: + // This is to align the indicator body to text rather than to framed rect + // (can be useful with radius=1). + ImGui::AlignTextToFramePadding(); + LoadingIndicatorCircle(config.specifiedLabel.c_str(), _radius, _mainColor, _optionalColor, _circleCount, _speed); + break; + case Style_Ring: + { + const float pixSize = (_radius > 0? _radius : 1.f) * ImGui::GetTextLineHeight(); + // `thickness=1` corresponds to line thickness being 1/4 of the ring radius, + // i.e. 1/8 of the diameter (pixSize). + const float pixThickness = (_thickness > 0? _thickness : 1.f) * pixSize * 0.125f; + // This is to align the indicator body to text rather than to framed rect + // (can be useful with radius=1). + ImGui::AlignTextToFramePadding(); + LoadingIndicatorRing(config.specifiedLabel.c_str(), pixSize, pixThickness, _speed, _mainColor); + } + break; + } } //----------------------------------------------------------------------------- @@ -98,13 +121,19 @@ void mvLoadingIndicator::handleSpecificKeywordArgs(PyObject* dict) if (dict == nullptr) return; - if (PyObject* item = PyDict_GetItemString(dict, "style")) _style = ToInt(item); + if (PyObject* item = PyDict_GetItemString(dict, "style")) _style = static_cast