From c756842b7bb92d3012cec2981d874588066048a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 10 Dec 2024 16:38:36 +0100 Subject: [PATCH 01/58] Im Ge debugger: Gray out disabled state (state that belongs to disabled features) --- GPU/Common/GPUDebugInterface.h | 2 +- GPU/GPUCommon.cpp | 2 +- GPU/GPUCommon.h | 2 +- UI/ImDebugger/ImGe.cpp | 9 +++++++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/GPU/Common/GPUDebugInterface.h b/GPU/Common/GPUDebugInterface.h index 101bb01b1828..b68288b0ee15 100644 --- a/GPU/Common/GPUDebugInterface.h +++ b/GPU/Common/GPUDebugInterface.h @@ -220,7 +220,7 @@ class GPUDebugInterface { virtual u32 GetRelativeAddress(u32 data) = 0; virtual u32 GetVertexAddress() = 0; virtual u32 GetIndexAddress() = 0; - virtual GPUgstate GetGState() = 0; + virtual const GPUgstate &GetGState() = 0; // Needs to be called from the GPU thread. // Calling from a separate thread (e.g. UI) may fail. virtual void SetCmdValue(u32 op) = 0; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 5764f30a51c2..4c296feb71e1 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -1688,7 +1688,7 @@ u32 GPUCommon::GetIndexAddress() { return gstate_c.indexAddr; } -GPUgstate GPUCommon::GetGState() { +const GPUgstate &GPUCommon::GetGState() { return gstate; } diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 305a04d7dadb..89465eca70a6 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -355,7 +355,7 @@ class GPUCommon : public GPUDebugInterface { u32 GetRelativeAddress(u32 data) override; u32 GetVertexAddress() override; u32 GetIndexAddress() override; - GPUgstate GetGState() override; + const GPUgstate &GetGState() override; void SetCmdValue(u32 op) override; DisplayList* getList(int listid) { diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index c40c2fc1c337..a0f104a058d1 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -348,6 +348,10 @@ void DrawGeStateWindow(ImConfig &cfg, GPUDebugInterface *gpuDebug) { for (size_t i = 0; i < numRows; i++) { const GECmdInfo &info = GECmdInfoByCmd(rows[i]); + const bool enabled = info.enableCmd == 0 || (gstate.cmdmem[info.enableCmd] & 1) == 1; + + if (!enabled) + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 128)); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("-"); // breakpoint @@ -356,13 +360,14 @@ void DrawGeStateWindow(ImConfig &cfg, GPUDebugInterface *gpuDebug) { ImGui::TableNextColumn(); char temp[256]; - const bool enabled = info.enableCmd == 0 || (gstate.cmdmem[info.enableCmd] & 1) == 1; const u32 value = gstate.cmdmem[info.cmd] & 0xFFFFFF; const u32 otherValue = gstate.cmdmem[info.otherCmd] & 0xFFFFFF; const u32 otherValue2 = gstate.cmdmem[info.otherCmd2] & 0xFFFFFF; - FormatStateRow(gpuDebug, temp, sizeof(temp), info.fmt, value, enabled, otherValue, otherValue2); + FormatStateRow(gpuDebug, temp, sizeof(temp), info.fmt, value, true, otherValue, otherValue2); ImGui::TextUnformatted(temp); + if (!enabled) + ImGui::PopStyleColor(); } ImGui::EndTable(); From d3789367a4e82d1e2a5e4fb3f0f0d58b42551a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 10 Dec 2024 17:21:30 +0100 Subject: [PATCH 02/58] ImGeDebugger: Implement the new state viewer --- GPU/Debugger/GECommandTable.cpp | 4 +- GPU/Debugger/GECommandTable.h | 5 +- GPU/Debugger/State.cpp | 182 -------------------------- UI/ImDebugger/ImGe.cpp | 223 ++++++++++++++++++++++++++++++-- Windows/GEDebugger/TabState.cpp | 189 ++++++++++++++++++++++++++- 5 files changed, 399 insertions(+), 204 deletions(-) diff --git a/GPU/Debugger/GECommandTable.cpp b/GPU/Debugger/GECommandTable.cpp index 3a08a8d19974..73898fdc6ceb 100644 --- a/GPU/Debugger/GECommandTable.cpp +++ b/GPU/Debugger/GECommandTable.cpp @@ -189,7 +189,7 @@ static constexpr GECmdInfo geCmdInfo[] = { { GE_CMD_LAC3, "light3ambient", GECmdFormat::RGB, "Light ambient 3", CMD_FMT_HEX, GE_CMD_LIGHTENABLE3 }, { GE_CMD_LDC3, "light3diffuse", GECmdFormat::RGB, "Light diffuse 3", CMD_FMT_HEX, GE_CMD_LIGHTENABLE3 }, { GE_CMD_LSC3, "light3specular", GECmdFormat::RGB, "Light specular 3", CMD_FMT_HEX, GE_CMD_LIGHTENABLE3 }, - { GE_CMD_CULL, "cullccw", GECmdFormat::FLAG, "Cull mode", CMD_FMT_FLAG, CMD_FMT_CULL, GE_CMD_CULLFACEENABLE}, + { GE_CMD_CULL, "cullccw", GECmdFormat::FLAG, "Cull mode", CMD_FMT_CULL, GE_CMD_CULLFACEENABLE}, { GE_CMD_FRAMEBUFPTR, "fbptr", GECmdFormat::LOW_ADDR_ONLY, "Framebuffer", CMD_FMT_PTRWIDTH, 0, GE_CMD_FRAMEBUFWIDTH}, { GE_CMD_FRAMEBUFWIDTH, "fbstride", GECmdFormat::STRIDE, "Framebuffer stride" }, { GE_CMD_ZBUFPTR, "zbptr", GECmdFormat::LOW_ADDR_ONLY, "Depth buffer", CMD_FMT_PTRWIDTH, 0, GE_CMD_ZBUFWIDTH}, @@ -252,7 +252,7 @@ static constexpr GECmdInfo geCmdInfo[] = { { GE_CMD_MAXZ, "maxz", GECmdFormat::DATA16, "Max Z", CMD_FMT_HEX}, { GE_CMD_COLORTEST, "ctestfunc", GECmdFormat::COLOR_TEST_FUNC, "Color test", CMD_FMT_COLORTEST, GE_CMD_COLORTESTENABLE, GE_CMD_COLORREF, GE_CMD_COLORTESTMASK}, { GE_CMD_COLORREF, "ctestref", GECmdFormat::RGB, "Color test ref", CMD_FMT_HEX, }, - { GE_CMD_COLORTESTMASK, "ctestmask", GECmdFormat::RGB, "Color test mask", CMD_FMT_HEX}, + { GE_CMD_COLORTESTMASK, "ctestmask", GECmdFormat::RGB, "Color test mask", CMD_FMT_HEX, GE_CMD_COLORTESTENABLE}, { GE_CMD_ALPHATEST, "atest", GECmdFormat::ALPHA_TEST, "Alpha test mask", CMD_FMT_ALPHATEST, GE_CMD_ALPHATESTENABLE}, { GE_CMD_STENCILTEST, "stest", GECmdFormat::ALPHA_TEST, "Stencil test", CMD_FMT_STENCILTEST, GE_CMD_STENCILTESTENABLE}, { GE_CMD_STENCILOP, "stencilop", GECmdFormat::STENCIL_OP, "Stencil op", CMD_FMT_STENCILOP, GE_CMD_STENCILTESTENABLE}, diff --git a/GPU/Debugger/GECommandTable.h b/GPU/Debugger/GECommandTable.h index 4bee175885a9..cbe3091cffda 100644 --- a/GPU/Debugger/GECommandTable.h +++ b/GPU/Debugger/GECommandTable.h @@ -18,7 +18,6 @@ #pragma once #include -#include #include "GPU/ge_constants.h" @@ -120,9 +119,9 @@ enum CmdFormatType { struct GECmdInfo { GECommand cmd; - std::string_view name; // scripting / expression name + const char *name; // scripting / expression name GECmdFormat cmdFmt; - std::string_view uiName; // friendly name + const char *uiName; // friendly name CmdFormatType fmt; uint8_t enableCmd; uint8_t otherCmd; diff --git a/GPU/Debugger/State.cpp b/GPU/Debugger/State.cpp index f424f980de48..4db525f9c481 100644 --- a/GPU/Debugger/State.cpp +++ b/GPU/Debugger/State.cpp @@ -8,188 +8,6 @@ #include "GPU/Common/VertexDecoderCommon.h" #include "Core/System.h" -const GECommand g_stateFlagsRows[] = { - GE_CMD_LIGHTINGENABLE, - GE_CMD_LIGHTENABLE0, - GE_CMD_LIGHTENABLE1, - GE_CMD_LIGHTENABLE2, - GE_CMD_LIGHTENABLE3, - GE_CMD_DEPTHCLAMPENABLE, - GE_CMD_CULLFACEENABLE, - GE_CMD_TEXTUREMAPENABLE, - GE_CMD_FOGENABLE, - GE_CMD_DITHERENABLE, - GE_CMD_ALPHABLENDENABLE, - GE_CMD_ALPHATESTENABLE, - GE_CMD_ZTESTENABLE, - GE_CMD_STENCILTESTENABLE, - GE_CMD_ANTIALIASENABLE, - GE_CMD_PATCHCULLENABLE, - GE_CMD_COLORTESTENABLE, - GE_CMD_LOGICOPENABLE, - GE_CMD_ZWRITEDISABLE, -}; -const size_t g_stateFlagsRowsSize = ARRAY_SIZE(g_stateFlagsRows); - -const GECommand g_stateLightingRows[] = { - GE_CMD_AMBIENTCOLOR, - GE_CMD_AMBIENTALPHA, - GE_CMD_MATERIALUPDATE, - GE_CMD_MATERIALEMISSIVE, - GE_CMD_MATERIALAMBIENT, - GE_CMD_MATERIALDIFFUSE, - GE_CMD_MATERIALALPHA, - GE_CMD_MATERIALSPECULAR, - GE_CMD_MATERIALSPECULARCOEF, - GE_CMD_REVERSENORMAL, - GE_CMD_SHADEMODE, - GE_CMD_LIGHTMODE, - GE_CMD_LIGHTTYPE0, - GE_CMD_LIGHTTYPE1, - GE_CMD_LIGHTTYPE2, - GE_CMD_LIGHTTYPE3, - GE_CMD_LX0, - GE_CMD_LX1, - GE_CMD_LX2, - GE_CMD_LX3, - GE_CMD_LDX0, - GE_CMD_LDX1, - GE_CMD_LDX2, - GE_CMD_LDX3, - GE_CMD_LKA0, - GE_CMD_LKA1, - GE_CMD_LKA2, - GE_CMD_LKA3, - GE_CMD_LKS0, - GE_CMD_LKS1, - GE_CMD_LKS2, - GE_CMD_LKS3, - GE_CMD_LKO0, - GE_CMD_LKO1, - GE_CMD_LKO2, - GE_CMD_LKO3, - GE_CMD_LAC0, - GE_CMD_LDC0, - GE_CMD_LSC0, - GE_CMD_LAC1, - GE_CMD_LDC1, - GE_CMD_LSC1, - GE_CMD_LAC2, - GE_CMD_LDC2, - GE_CMD_LSC2, - GE_CMD_LAC3, - GE_CMD_LDC3, - GE_CMD_LSC3, -}; -const size_t g_stateLightingRowsSize = ARRAY_SIZE(g_stateLightingRows); - -const GECommand g_stateTextureRows[] = { - GE_CMD_TEXADDR0, - GE_CMD_TEXSIZE0, - GE_CMD_TEXFORMAT, - GE_CMD_CLUTADDR, - GE_CMD_CLUTFORMAT, - GE_CMD_TEXSCALEU, - GE_CMD_TEXSCALEV, - GE_CMD_TEXOFFSETU, - GE_CMD_TEXOFFSETV, - GE_CMD_TEXMAPMODE, - GE_CMD_TEXSHADELS, - GE_CMD_TEXFUNC, - GE_CMD_TEXENVCOLOR, - GE_CMD_TEXMODE, - GE_CMD_TEXFILTER, - GE_CMD_TEXWRAP, - GE_CMD_TEXLEVEL, - GE_CMD_TEXLODSLOPE, - GE_CMD_TEXADDR1, - GE_CMD_TEXADDR2, - GE_CMD_TEXADDR3, - GE_CMD_TEXADDR4, - GE_CMD_TEXADDR5, - GE_CMD_TEXADDR6, - GE_CMD_TEXADDR7, - GE_CMD_TEXSIZE1, - GE_CMD_TEXSIZE2, - GE_CMD_TEXSIZE3, - GE_CMD_TEXSIZE4, - GE_CMD_TEXSIZE5, - GE_CMD_TEXSIZE6, - GE_CMD_TEXSIZE7, -}; -const size_t g_stateTextureRowsSize = ARRAY_SIZE(g_stateTextureRows); - -const GECommand g_stateSettingsRows[] = { - GE_CMD_FRAMEBUFPTR, - GE_CMD_FRAMEBUFPIXFORMAT, - GE_CMD_ZBUFPTR, - GE_CMD_VIEWPORTXSCALE, - GE_CMD_VIEWPORTXCENTER, - GE_CMD_SCISSOR1, - GE_CMD_REGION1, - GE_CMD_COLORTEST, - GE_CMD_ALPHATEST, - GE_CMD_CLEARMODE, - GE_CMD_STENCILTEST, - GE_CMD_STENCILOP, - GE_CMD_ZTEST, - GE_CMD_MASKRGB, - GE_CMD_MASKALPHA, - GE_CMD_TRANSFERSRC, - GE_CMD_TRANSFERSRCPOS, - GE_CMD_TRANSFERDST, - GE_CMD_TRANSFERDSTPOS, - GE_CMD_TRANSFERSIZE, - GE_CMD_VERTEXTYPE, - GE_CMD_OFFSETADDR, - GE_CMD_VADDR, - GE_CMD_IADDR, - GE_CMD_MINZ, - GE_CMD_MAXZ, - GE_CMD_OFFSETX, - GE_CMD_CULL, - GE_CMD_BLENDMODE, - GE_CMD_BLENDFIXEDA, - GE_CMD_BLENDFIXEDB, - GE_CMD_LOGICOP, - GE_CMD_FOG1, - GE_CMD_FOG2, - GE_CMD_FOGCOLOR, - GE_CMD_MORPHWEIGHT0, - GE_CMD_MORPHWEIGHT1, - GE_CMD_MORPHWEIGHT2, - GE_CMD_MORPHWEIGHT3, - GE_CMD_MORPHWEIGHT4, - GE_CMD_MORPHWEIGHT5, - GE_CMD_MORPHWEIGHT6, - GE_CMD_MORPHWEIGHT7, - GE_CMD_PATCHDIVISION, - GE_CMD_PATCHPRIMITIVE, - GE_CMD_PATCHFACING, - GE_CMD_DITH0, - GE_CMD_DITH1, - GE_CMD_DITH2, - GE_CMD_DITH3, - GE_CMD_VSCX, - GE_CMD_VSCZ, - GE_CMD_VTCS, - GE_CMD_VCV, - GE_CMD_VSCV, - GE_CMD_VFC, - GE_CMD_VAP, -}; -const size_t g_stateSettingsRowsSize = ARRAY_SIZE(g_stateSettingsRows); - -// TODO: Commands not present in the above lists (some because they don't have meaningful values...): -// GE_CMD_PRIM, GE_CMD_BEZIER, GE_CMD_SPLINE, GE_CMD_BOUNDINGBOX, -// GE_CMD_JUMP, GE_CMD_BJUMP, GE_CMD_CALL, GE_CMD_RET, GE_CMD_END, GE_CMD_SIGNAL, GE_CMD_FINISH, -// GE_CMD_BONEMATRIXNUMBER, GE_CMD_BONEMATRIXDATA, GE_CMD_WORLDMATRIXNUMBER, GE_CMD_WORLDMATRIXDATA, -// GE_CMD_VIEWMATRIXNUMBER, GE_CMD_VIEWMATRIXDATA, GE_CMD_PROJMATRIXNUMBER, GE_CMD_PROJMATRIXDATA, -// GE_CMD_TGENMATRIXNUMBER, GE_CMD_TGENMATRIXDATA, -// GE_CMD_LOADCLUT, GE_CMD_TEXFLUSH, GE_CMD_TEXSYNC, -// GE_CMD_TRANSFERSTART, -// GE_CMD_UNKNOWN_* - void FormatStateRow(GPUDebugInterface *gpudebug, char *dest, size_t destSize, CmdFormatType fmt, u32 value, bool enabled, u32 otherValue, u32 otherValue2) { switch (fmt) { case CMD_FMT_HEX: diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index a0f104a058d1..1b086f6f0fd1 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -330,6 +330,181 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, GPUDebugInterface *gpuDebug) { ImGui::End(); } +struct StateItem { + bool header; GECommand cmd; const char *title; bool closedByDefault; +}; + +static const StateItem g_rasterState[] = { + {true, GE_CMD_NOP, "Framebuffer"}, + {false, GE_CMD_FRAMEBUFPTR}, + {false, GE_CMD_FRAMEBUFPIXFORMAT}, + {false, GE_CMD_CLEARMODE}, + + {true, GE_CMD_ZTESTENABLE}, + {false, GE_CMD_ZBUFPTR}, + {false, GE_CMD_ZTEST}, + {false, GE_CMD_ZWRITEDISABLE}, + + {true, GE_CMD_STENCILTESTENABLE}, + {false, GE_CMD_STENCILTEST}, + {false, GE_CMD_STENCILOP}, + + {true, GE_CMD_ALPHABLENDENABLE}, + {false, GE_CMD_BLENDMODE}, + {false, GE_CMD_BLENDFIXEDA}, + {false, GE_CMD_BLENDFIXEDB}, + + {true, GE_CMD_ALPHATESTENABLE}, + {false, GE_CMD_ALPHATEST}, + + {true, GE_CMD_COLORTESTENABLE}, + {false, GE_CMD_COLORTEST}, + {false, GE_CMD_COLORTESTMASK}, + + {true, GE_CMD_FOGENABLE}, + {false, GE_CMD_FOGCOLOR}, + {false, GE_CMD_FOG1}, + {false, GE_CMD_FOG2}, + + {true, GE_CMD_CULLFACEENABLE}, + {false, GE_CMD_CULL}, + + {true, GE_CMD_LOGICOPENABLE}, + {false, GE_CMD_LOGICOP}, + + {true, GE_CMD_NOP, "Clipping/Clamping"}, + {false, GE_CMD_MINZ}, + {false, GE_CMD_MAXZ}, + {false, GE_CMD_DEPTHCLAMPENABLE}, + + {true, GE_CMD_NOP, "Other raster state"}, + {false, GE_CMD_MASKRGB}, + {false, GE_CMD_MASKALPHA}, + {false, GE_CMD_SCISSOR1}, + {false, GE_CMD_REGION1}, + {false, GE_CMD_OFFSETX}, + {false, GE_CMD_DITH0}, + {false, GE_CMD_DITH1}, + {false, GE_CMD_DITH2}, + {false, GE_CMD_DITH3}, +}; + +static const StateItem g_textureState[] = { + {true, GE_CMD_TEXTUREMAPENABLE}, + {false, GE_CMD_TEXADDR0}, + {false, GE_CMD_TEXSIZE0}, + {false, GE_CMD_TEXENVCOLOR}, + {false, GE_CMD_TEXMAPMODE}, + {false, GE_CMD_TEXSHADELS}, + {false, GE_CMD_TEXFORMAT}, + {false, GE_CMD_CLUTFORMAT}, + {false, GE_CMD_TEXFILTER}, + {false, GE_CMD_TEXWRAP}, + {false, GE_CMD_TEXLEVEL}, + {false, GE_CMD_TEXFUNC}, + {false, GE_CMD_TEXLODSLOPE}, + + {false, GE_CMD_TEXSCALEU}, + {false, GE_CMD_TEXSCALEV}, + {false, GE_CMD_TEXOFFSETU}, + {false, GE_CMD_TEXOFFSETV}, + + {true, GE_CMD_NOP, "Additional mips", true}, + {false, GE_CMD_TEXADDR1}, + {false, GE_CMD_TEXADDR2}, + {false, GE_CMD_TEXADDR3}, + {false, GE_CMD_TEXADDR4}, + {false, GE_CMD_TEXADDR5}, + {false, GE_CMD_TEXADDR6}, + {false, GE_CMD_TEXADDR7}, + {false, GE_CMD_TEXSIZE1}, + {false, GE_CMD_TEXSIZE2}, + {false, GE_CMD_TEXSIZE3}, + {false, GE_CMD_TEXSIZE4}, + {false, GE_CMD_TEXSIZE5}, + {false, GE_CMD_TEXSIZE6}, + {false, GE_CMD_TEXSIZE7}, +}; + +static const StateItem g_lightingState[] = { + {false, GE_CMD_AMBIENTCOLOR}, + {false, GE_CMD_AMBIENTALPHA}, + {false, GE_CMD_MATERIALUPDATE}, + {false, GE_CMD_MATERIALEMISSIVE}, + {false, GE_CMD_MATERIALAMBIENT}, + {false, GE_CMD_MATERIALDIFFUSE}, + {false, GE_CMD_MATERIALALPHA}, + {false, GE_CMD_MATERIALSPECULAR}, + {false, GE_CMD_MATERIALSPECULARCOEF}, + {false, GE_CMD_REVERSENORMAL}, + {false, GE_CMD_SHADEMODE}, + {false, GE_CMD_LIGHTMODE}, + {false, GE_CMD_LIGHTTYPE0}, + {false, GE_CMD_LIGHTTYPE1}, + {false, GE_CMD_LIGHTTYPE2}, + {false, GE_CMD_LIGHTTYPE3}, + {false, GE_CMD_LX0}, + {false, GE_CMD_LX1}, + {false, GE_CMD_LX2}, + {false, GE_CMD_LX3}, + {false, GE_CMD_LDX0}, + {false, GE_CMD_LDX1}, + {false, GE_CMD_LDX2}, + {false, GE_CMD_LDX3}, + {false, GE_CMD_LKA0}, + {false, GE_CMD_LKA1}, + {false, GE_CMD_LKA2}, + {false, GE_CMD_LKA3}, + {false, GE_CMD_LKS0}, + {false, GE_CMD_LKS1}, + {false, GE_CMD_LKS2}, + {false, GE_CMD_LKS3}, + {false, GE_CMD_LKO0}, + {false, GE_CMD_LKO1}, + {false, GE_CMD_LKO2}, + {false, GE_CMD_LKO3}, + {false, GE_CMD_LAC0}, + {false, GE_CMD_LDC0}, + {false, GE_CMD_LSC0}, + {false, GE_CMD_LAC1}, + {false, GE_CMD_LDC1}, + {false, GE_CMD_LSC1}, + {false, GE_CMD_LAC2}, + {false, GE_CMD_LDC2}, + {false, GE_CMD_LSC2}, + {false, GE_CMD_LAC3}, + {false, GE_CMD_LDC3}, + {false, GE_CMD_LSC3}, +}; + +static const StateItem g_vertexState[] = { + {true, GE_CMD_NOP, "Vertex type and transform"}, + {false, GE_CMD_VERTEXTYPE}, + {false, GE_CMD_VADDR}, + {false, GE_CMD_IADDR}, + {false, GE_CMD_OFFSETADDR}, + {false, GE_CMD_VIEWPORTXSCALE}, + {false, GE_CMD_VIEWPORTXCENTER}, + {false, GE_CMD_MORPHWEIGHT0}, + {false, GE_CMD_MORPHWEIGHT1}, + {false, GE_CMD_MORPHWEIGHT2}, + {false, GE_CMD_MORPHWEIGHT3}, + {false, GE_CMD_MORPHWEIGHT4}, + {false, GE_CMD_MORPHWEIGHT5}, + {false, GE_CMD_MORPHWEIGHT6}, + {false, GE_CMD_MORPHWEIGHT7}, + {false, GE_CMD_TEXSCALEU}, + {false, GE_CMD_TEXSCALEV}, + {false, GE_CMD_TEXOFFSETU}, + {false, GE_CMD_TEXOFFSETV}, + + {true, GE_CMD_NOP, "Tessellation"}, + {false, GE_CMD_PATCHPRIMITIVE}, + {false, GE_CMD_PATCHDIVISION}, + {false, GE_CMD_PATCHCULLENABLE}, + {false, GE_CMD_PATCHFACING}, +}; + // TODO: Separate window or merge into Ge debugger? void DrawGeStateWindow(ImConfig &cfg, GPUDebugInterface *gpuDebug) { ImGui::SetNextWindowSize(ImVec2(300, 500), ImGuiCond_FirstUseEver); @@ -338,26 +513,43 @@ void DrawGeStateWindow(ImConfig &cfg, GPUDebugInterface *gpuDebug) { return; } if (ImGui::BeginTabBar("GeRegs", ImGuiTabBarFlags_None)) { - auto buildStateTab = [&](const char *tabName, const GECommand *rows, size_t numRows) { + auto buildStateTab = [&](const char *tabName, const StateItem *rows, size_t numRows) { if (ImGui::BeginTabItem(tabName)) { if (ImGui::BeginTable("fpr", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) { - ImGui::TableSetupColumn("bkpt", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("State", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("bkpt", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableHeadersRow(); + bool anySection = false; + bool sectionOpen = false; for (size_t i = 0; i < numRows; i++) { - const GECmdInfo &info = GECmdInfoByCmd(rows[i]); + const GECmdInfo &info = GECmdInfoByCmd(rows[i].cmd); - const bool enabled = info.enableCmd == 0 || (gstate.cmdmem[info.enableCmd] & 1) == 1; + if (rows[i].header) { + anySection = true; + if (sectionOpen) { + ImGui::TreePop(); + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + sectionOpen = ImGui::TreeNodeEx(rows[i].cmd ? info.uiName : rows[i].title, rows[i].closedByDefault ? 0 : ImGuiTreeNodeFlags_DefaultOpen); + ImGui::TableNextColumn(); + } else { + if (!sectionOpen && anySection) { + continue; + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + } + const bool enabled = info.enableCmd == 0 || (gstate.cmdmem[info.enableCmd] & 1) == 1; if (!enabled) ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 128)); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("-"); // breakpoint - ImGui::TableNextColumn(); - ImGui::TextUnformatted(info.uiName.data(), info.uiName.data() + info.uiName.size()); - ImGui::TableNextColumn(); + if (!rows[i].header) { + ImGui::TextUnformatted(info.uiName); + ImGui::TableNextColumn(); + } char temp[256]; const u32 value = gstate.cmdmem[info.cmd] & 0xFFFFFF; @@ -369,6 +561,9 @@ void DrawGeStateWindow(ImConfig &cfg, GPUDebugInterface *gpuDebug) { if (!enabled) ImGui::PopStyleColor(); } + if (sectionOpen) { + ImGui::TreePop(); + } ImGui::EndTable(); } @@ -376,10 +571,10 @@ void DrawGeStateWindow(ImConfig &cfg, GPUDebugInterface *gpuDebug) { } }; - buildStateTab("Flags", g_stateFlagsRows, g_stateFlagsRowsSize); - buildStateTab("Lighting", g_stateLightingRows, g_stateLightingRowsSize); - buildStateTab("Texture", g_stateTextureRows, g_stateTextureRowsSize); - buildStateTab("Settings", g_stateSettingsRows, g_stateSettingsRowsSize); + buildStateTab("Raster", g_rasterState, ARRAY_SIZE(g_rasterState)); + buildStateTab("Texture", g_textureState, ARRAY_SIZE(g_textureState)); + buildStateTab("Lighting", g_lightingState, ARRAY_SIZE(g_lightingState)); + buildStateTab("Transform/Tess", g_vertexState, ARRAY_SIZE(g_vertexState)); // Do a vertex tab (maybe later a separate window) if (ImGui::BeginTabItem("Vertices")) { diff --git a/Windows/GEDebugger/TabState.cpp b/Windows/GEDebugger/TabState.cpp index ecb9b9f30c6b..01c224058fd7 100644 --- a/Windows/GEDebugger/TabState.cpp +++ b/Windows/GEDebugger/TabState.cpp @@ -32,6 +32,7 @@ #include "Windows/W32Util/ContextMenu.h" #include "GPU/GPUState.h" #include "GPU/GeDisasm.h" +#include "GPU/Debugger/GECommandTable.h" #include "GPU/Common/GPUDebugInterface.h" #include "GPU/Debugger/Breakpoints.h" #include "GPU/Debugger/Stepping.h" @@ -61,6 +62,188 @@ enum StateValuesCols { static std::vector watchList; +const GECommand g_stateFlagsRows[] = { + GE_CMD_LIGHTINGENABLE, + GE_CMD_LIGHTENABLE0, + GE_CMD_LIGHTENABLE1, + GE_CMD_LIGHTENABLE2, + GE_CMD_LIGHTENABLE3, + GE_CMD_DEPTHCLAMPENABLE, + GE_CMD_CULLFACEENABLE, + GE_CMD_TEXTUREMAPENABLE, + GE_CMD_FOGENABLE, + GE_CMD_DITHERENABLE, + GE_CMD_ALPHABLENDENABLE, + GE_CMD_ALPHATESTENABLE, + GE_CMD_ZTESTENABLE, + GE_CMD_STENCILTESTENABLE, + GE_CMD_ANTIALIASENABLE, + GE_CMD_PATCHCULLENABLE, + GE_CMD_COLORTESTENABLE, + GE_CMD_LOGICOPENABLE, + GE_CMD_ZWRITEDISABLE, +}; +const size_t g_stateFlagsRowsSize = ARRAY_SIZE(g_stateFlagsRows); + +const GECommand g_stateLightingRows[] = { + GE_CMD_AMBIENTCOLOR, + GE_CMD_AMBIENTALPHA, + GE_CMD_MATERIALUPDATE, + GE_CMD_MATERIALEMISSIVE, + GE_CMD_MATERIALAMBIENT, + GE_CMD_MATERIALDIFFUSE, + GE_CMD_MATERIALALPHA, + GE_CMD_MATERIALSPECULAR, + GE_CMD_MATERIALSPECULARCOEF, + GE_CMD_REVERSENORMAL, + GE_CMD_SHADEMODE, + GE_CMD_LIGHTMODE, + GE_CMD_LIGHTTYPE0, + GE_CMD_LIGHTTYPE1, + GE_CMD_LIGHTTYPE2, + GE_CMD_LIGHTTYPE3, + GE_CMD_LX0, + GE_CMD_LX1, + GE_CMD_LX2, + GE_CMD_LX3, + GE_CMD_LDX0, + GE_CMD_LDX1, + GE_CMD_LDX2, + GE_CMD_LDX3, + GE_CMD_LKA0, + GE_CMD_LKA1, + GE_CMD_LKA2, + GE_CMD_LKA3, + GE_CMD_LKS0, + GE_CMD_LKS1, + GE_CMD_LKS2, + GE_CMD_LKS3, + GE_CMD_LKO0, + GE_CMD_LKO1, + GE_CMD_LKO2, + GE_CMD_LKO3, + GE_CMD_LAC0, + GE_CMD_LDC0, + GE_CMD_LSC0, + GE_CMD_LAC1, + GE_CMD_LDC1, + GE_CMD_LSC1, + GE_CMD_LAC2, + GE_CMD_LDC2, + GE_CMD_LSC2, + GE_CMD_LAC3, + GE_CMD_LDC3, + GE_CMD_LSC3, +}; +const size_t g_stateLightingRowsSize = ARRAY_SIZE(g_stateLightingRows); + +const GECommand g_stateTextureRows[] = { + GE_CMD_TEXADDR0, + GE_CMD_TEXSIZE0, + GE_CMD_TEXFORMAT, + GE_CMD_CLUTADDR, + GE_CMD_CLUTFORMAT, + GE_CMD_TEXSCALEU, + GE_CMD_TEXSCALEV, + GE_CMD_TEXOFFSETU, + GE_CMD_TEXOFFSETV, + GE_CMD_TEXMAPMODE, + GE_CMD_TEXSHADELS, + GE_CMD_TEXFUNC, + GE_CMD_TEXENVCOLOR, + GE_CMD_TEXMODE, + GE_CMD_TEXFILTER, + GE_CMD_TEXWRAP, + GE_CMD_TEXLEVEL, + GE_CMD_TEXLODSLOPE, + GE_CMD_TEXADDR1, + GE_CMD_TEXADDR2, + GE_CMD_TEXADDR3, + GE_CMD_TEXADDR4, + GE_CMD_TEXADDR5, + GE_CMD_TEXADDR6, + GE_CMD_TEXADDR7, + GE_CMD_TEXSIZE1, + GE_CMD_TEXSIZE2, + GE_CMD_TEXSIZE3, + GE_CMD_TEXSIZE4, + GE_CMD_TEXSIZE5, + GE_CMD_TEXSIZE6, + GE_CMD_TEXSIZE7, +}; +const size_t g_stateTextureRowsSize = ARRAY_SIZE(g_stateTextureRows); + +const GECommand g_stateSettingsRows[] = { + GE_CMD_FRAMEBUFPTR, + GE_CMD_FRAMEBUFPIXFORMAT, + GE_CMD_ZBUFPTR, + GE_CMD_VIEWPORTXSCALE, + GE_CMD_VIEWPORTXCENTER, + GE_CMD_SCISSOR1, + GE_CMD_REGION1, + GE_CMD_COLORTEST, + GE_CMD_ALPHATEST, + GE_CMD_CLEARMODE, + GE_CMD_STENCILTEST, + GE_CMD_STENCILOP, + GE_CMD_ZTEST, + GE_CMD_MASKRGB, + GE_CMD_MASKALPHA, + GE_CMD_TRANSFERSRC, + GE_CMD_TRANSFERSRCPOS, + GE_CMD_TRANSFERDST, + GE_CMD_TRANSFERDSTPOS, + GE_CMD_TRANSFERSIZE, + GE_CMD_VERTEXTYPE, + GE_CMD_OFFSETADDR, + GE_CMD_VADDR, + GE_CMD_IADDR, + GE_CMD_MINZ, + GE_CMD_MAXZ, + GE_CMD_OFFSETX, + GE_CMD_CULL, + GE_CMD_BLENDMODE, + GE_CMD_BLENDFIXEDA, + GE_CMD_BLENDFIXEDB, + GE_CMD_LOGICOP, + GE_CMD_FOG1, + GE_CMD_FOG2, + GE_CMD_FOGCOLOR, + GE_CMD_MORPHWEIGHT0, + GE_CMD_MORPHWEIGHT1, + GE_CMD_MORPHWEIGHT2, + GE_CMD_MORPHWEIGHT3, + GE_CMD_MORPHWEIGHT4, + GE_CMD_MORPHWEIGHT5, + GE_CMD_MORPHWEIGHT6, + GE_CMD_MORPHWEIGHT7, + GE_CMD_PATCHDIVISION, + GE_CMD_PATCHPRIMITIVE, + GE_CMD_PATCHFACING, + GE_CMD_DITH0, + GE_CMD_DITH1, + GE_CMD_DITH2, + GE_CMD_DITH3, + GE_CMD_VSCX, + GE_CMD_VSCZ, + GE_CMD_VTCS, + GE_CMD_VCV, + GE_CMD_VSCV, + GE_CMD_VFC, + GE_CMD_VAP, +}; +const size_t g_stateSettingsRowsSize = ARRAY_SIZE(g_stateSettingsRows); + +// TODO: Commands not present in the above lists (some because they don't have meaningful values...): +// GE_CMD_PRIM, GE_CMD_BEZIER, GE_CMD_SPLINE, GE_CMD_BOUNDINGBOX, +// GE_CMD_JUMP, GE_CMD_BJUMP, GE_CMD_CALL, GE_CMD_RET, GE_CMD_END, GE_CMD_SIGNAL, GE_CMD_FINISH, +// GE_CMD_BONEMATRIXNUMBER, GE_CMD_BONEMATRIXDATA, GE_CMD_WORLDMATRIXNUMBER, GE_CMD_WORLDMATRIXDATA, +// GE_CMD_VIEWMATRIXNUMBER, GE_CMD_VIEWMATRIXDATA, GE_CMD_PROJMATRIXNUMBER, GE_CMD_PROJMATRIXDATA, +// GE_CMD_TGENMATRIXNUMBER, GE_CMD_TGENMATRIXDATA, +// GE_CMD_LOADCLUT, GE_CMD_TEXFLUSH, GE_CMD_TEXSYNC, +// GE_CMD_TRANSFERSTART, +// GE_CMD_UNKNOWN_* + static void ToggleWatchList(const GECommand cmd) { for (size_t i = 0; i < watchList.size(); ++i) { if (watchList[i] == cmd) { @@ -175,21 +358,21 @@ void CtrlStateValues::OnDoubleClick(int row, int column) { const auto state = gpuDebug->GetGState(); u32 newValue = state.cmdmem[info.cmd] & 0x00FFFFFF; - snprintf(title, sizeof(title), "New value for %.*s", (int)info.uiName.size(), info.uiName.data()); + snprintf(title, sizeof(title), "New value for %s", info.uiName); if (PromptStateValue(info, GetHandle(), title, newValue)) { newValue |= state.cmdmem[info.cmd] & 0xFF000000; SetCmdValue(newValue); if (info.otherCmd) { newValue = state.cmdmem[info.otherCmd] & 0x00FFFFFF; - snprintf(title, sizeof(title), "New value for %.*s (secondary)", (int)info.uiName.size(), info.uiName.data()); + snprintf(title, sizeof(title), "New value for %s (secondary)", info.uiName); if (PromptStateValue(info, GetHandle(), title, newValue)) { newValue |= state.cmdmem[info.otherCmd] & 0xFF000000; SetCmdValue(newValue); if (info.otherCmd2) { newValue = state.cmdmem[info.otherCmd2] & 0x00FFFFFF; - snprintf(title, sizeof(title), "New value for %.*s (tertiary)", (int)info.uiName.size(), info.uiName.data()); + snprintf(title, sizeof(title), "New value for %s (tertiary)", info.uiName); if (PromptStateValue(info, GetHandle(), title, newValue)) { newValue |= state.cmdmem[info.otherCmd2] & 0xFF000000; SetCmdValue(newValue); From 61d2459e94ec80f7c0f8050f8ce622981e4a4128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 10 Dec 2024 16:07:54 +0100 Subject: [PATCH 03/58] Minor code cleanup --- GPU/Common/TextureReplacer.cpp | 3 +++ GPU/Common/TextureReplacer.h | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/GPU/Common/TextureReplacer.cpp b/GPU/Common/TextureReplacer.cpp index 6842e2490d40..8c3043b8fb2a 100644 --- a/GPU/Common/TextureReplacer.cpp +++ b/GPU/Common/TextureReplacer.cpp @@ -506,10 +506,13 @@ u32 TextureReplacer::ComputeHash(u32 addr, int bufw, int w, int h, bool swizzled } const u8 *checkp = Memory::GetPointerUnchecked(addr); + + float reduceHashSize = 1.0f; if (reduceHash_) { reduceHashSize = LookupReduceHashRange(w, h); // default to reduceHashGlobalValue which default is 0.5 } + if (bufw <= w) { // We can assume the data is contiguous. These are the total used pixels. const u32 totalPixels = bufw * h + (w - bufw); diff --git a/GPU/Common/TextureReplacer.h b/GPU/Common/TextureReplacer.h index d71c36b09c7c..9762bcd8295d 100644 --- a/GPU/Common/TextureReplacer.h +++ b/GPU/Common/TextureReplacer.h @@ -150,7 +150,6 @@ class TextureReplacer { bool reduceHash_ = false; bool ignoreMipmap_ = false; - float reduceHashSize = 1.0f; // default value with reduceHash to false float reduceHashGlobalValue = 0.5f; // Global value for textures dump pngs of all sizes, 0.5 by default but can be set in textures.ini double lastTextureCacheSizeGB_ = 0.0; From ec19c47b89e358659a2aee2cf1ce031585e93579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 10 Dec 2024 16:21:45 +0100 Subject: [PATCH 04/58] Add special texture hashing mode solving the Tag Force problem. Don't want to build some complicated rule-based thing until we have more use cases, so this is quite specialized. See #19714 --- GPU/Common/TextureReplacer.cpp | 9 ++++++++- GPU/Common/TextureReplacer.h | 1 + GPU/Vulkan/TextureCacheVulkan.cpp | 2 -- UI/GameSettingsScreen.cpp | 1 + 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/GPU/Common/TextureReplacer.cpp b/GPU/Common/TextureReplacer.cpp index 8c3043b8fb2a..aa2ecc022ad0 100644 --- a/GPU/Common/TextureReplacer.cpp +++ b/GPU/Common/TextureReplacer.cpp @@ -301,6 +301,7 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, VFSBackend *dir, bool isOverri // Multiplies sizeInRAM/bytesPerLine in XXHASH by 0.5. options->Get("reduceHash", &reduceHash_, reduceHash_); options->Get("ignoreMipmap", &ignoreMipmap_, ignoreMipmap_); + options->Get("skipLastDXT1Blocks128x64", &skipLastDXT1Blocks128x64_, skipLastDXT1Blocks128x64_); if (reduceHash_ && hash_ == ReplacedTextureHash::QUICK) { reduceHash_ = false; ERROR_LOG(Log::TexReplacement, "Texture Replacement: reduceHash option requires safer hash, use xxh32 or xxh64 instead."); @@ -516,7 +517,7 @@ u32 TextureReplacer::ComputeHash(u32 addr, int bufw, int w, int h, bool swizzled if (bufw <= w) { // We can assume the data is contiguous. These are the total used pixels. const u32 totalPixels = bufw * h + (w - bufw); - const u32 sizeInRAM = (textureBitsPerPixel[fmt] * totalPixels) / 8 * reduceHashSize; + u32 sizeInRAM = (textureBitsPerPixel[fmt] * totalPixels) / 8 * reduceHashSize; // Sanity check: Ignore textures that are at the end of RAM. if (Memory::MaxSizeAtAddress(addr) < sizeInRAM) { @@ -524,6 +525,12 @@ u32 TextureReplacer::ComputeHash(u32 addr, int bufw, int w, int h, bool swizzled return 0; } + // Hack for Yu Gi Oh texture hashing problem. See issue #19714 + if (skipLastDXT1Blocks128x64_ && fmt == GE_TFMT_DXT1 && w == 128 && h == 64) { + // Skip the last few blocks as specified. + sizeInRAM -= 8 * skipLastDXT1Blocks128x64_; + } + switch (hash_) { case ReplacedTextureHash::QUICK: return StableQuickTexHash(checkp, sizeInRAM); diff --git a/GPU/Common/TextureReplacer.h b/GPU/Common/TextureReplacer.h index 9762bcd8295d..ea70bc09ea0b 100644 --- a/GPU/Common/TextureReplacer.h +++ b/GPU/Common/TextureReplacer.h @@ -149,6 +149,7 @@ class TextureReplacer { bool ignoreAddress_ = false; bool reduceHash_ = false; bool ignoreMipmap_ = false; + int skipLastDXT1Blocks128x64_ = 0; float reduceHashGlobalValue = 0.5f; // Global value for textures dump pngs of all sizes, 0.5 by default but can be set in textures.ini diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 9449ed5190af..1520358cadaf 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -652,8 +652,6 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { } // Format might be wrong in lowMemoryMode_, so don't save. if (plan.saveTexture && !lowMemoryMode_) { - INFO_LOG(Log::G3D, "Calling NotifyTextureDecoded %08x", entry->addr); - // When hardware texture scaling is enabled, this saves the original. int w = dataScaled ? mipWidth : mipUnscaledWidth; int h = dataScaled ? mipHeight : mipUnscaledHeight; diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 428301a83d0a..68772aeb3807 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -2089,6 +2089,7 @@ UI::EventReturn DeveloperToolsScreen::OnLoggingChanged(UI::EventParams &e) { } UI::EventReturn DeveloperToolsScreen::OnRunCPUTests(UI::EventParams &e) { + // TODO: If game is loaded, don't do anything. #if !PPSSPP_PLATFORM(UWP) RunTests(); #endif From cccdfad0ba62c3063d6a948257494c3c9ab639a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 10 Dec 2024 21:50:14 +0100 Subject: [PATCH 05/58] Show basic framebuffer preview --- Core/Core.cpp | 2 +- UI/ImDebugger/ImGe.cpp | 63 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/Core/Core.cpp b/Core/Core.cpp index 3658d330ea7d..94bbdf5c7441 100644 --- a/Core/Core.cpp +++ b/Core/Core.cpp @@ -395,8 +395,8 @@ void Core_Break(const char *reason, u32 relatedAddress) { return; } - // Stop the tracer { + // Stop the tracer std::lock_guard lock(g_stepMutex); if (!g_cpuStepCommand.empty() && Core_IsStepping()) { // If we're in a failed step that uses a temp breakpoint, we need to be able to override it here. diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index 1b086f6f0fd1..f71a90f0750f 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -311,15 +311,19 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, GPUDebugInterface *gpuDebug) { } } - // Let's display the current CLUT. + // First, let's list any active display lists in the left column, on top of the disassembly. + + ImGui::BeginChild("left pane", ImVec2(400, 0), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeX); - // First, let's list any active display lists in the left column. for (auto index : gpuDebug->GetDisplayListQueue()) { const auto &list = gpuDebug->GetDisplayList(index); char title[64]; snprintf(title, sizeof(title), "List %d", list.id); if (ImGui::CollapsingHeader(title, ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Text("PC: %08x (start: %08x)", list.pc, list.startpc); + ImGui::Text("PC: %08x", list.pc); + ImGui::Text("StartPC: %08x", list.startpc); + ImGui::Text("Pending interrupt: %d", (int)list.pendingInterrupt); + ImGui::Text("Stack depth: %d", (int)list.stackptr); ImGui::Text("BBOX result: %d", (int)list.bboxResult); } } @@ -327,6 +331,59 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, GPUDebugInterface *gpuDebug) { // Display the disassembly view. disasmView_.Draw(gpuDebug); + ImGui::EndChild(); + + ImGui::SameLine(); + + ImGui::BeginChild("texture/fb view"); // Leave room for 1 line below us + + if (coreState == CORE_STEPPING_GE) { + FramebufferManagerCommon *fbman = gpuDebug->GetFramebufferManagerCommon(); + u32 fbptr = gstate.getFrameBufAddress(); + VirtualFramebuffer *vfb = fbman->GetVFBAt(fbptr); + + if (vfb) { + if (vfb->fbo) { + ImGui::Text("Framebuffer: %s", vfb->fbo->Tag()); + } else { + ImGui::Text("Framebuffer"); + } + + if (ImGui::BeginTabBar("aspects")) { + if (ImGui::BeginTabItem("Color")) { + ImTextureID texId = ImGui_ImplThin3d_AddFBAsTextureTemp(vfb->fbo, Draw::FB_COLOR_BIT, ImGuiPipeline::TexturedOpaque); + ImGui::Image(texId, ImVec2(vfb->width, vfb->height)); + // TODO: Draw vertex preview on top! + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Depth")) { + ImTextureID texId = ImGui_ImplThin3d_AddFBAsTextureTemp(vfb->fbo, Draw::FB_DEPTH_BIT, ImGuiPipeline::TexturedOpaque); + ImGui::Image(texId, ImVec2(128, 128)); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Stencil")) { + ImTextureID texId = ImGui_ImplThin3d_AddFBAsTextureTemp(vfb->fbo, Draw::FB_STENCIL_BIT, ImGuiPipeline::TexturedOpaque); + ImGui::Image(texId, ImVec2(128, 128)); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + + ImGui::Text("%dx%d (emulated: %dx%d)", vfb->width, vfb->height, vfb->bufferWidth, vfb->bufferHeight); + } + + ImGui::Text("Texture: "); + + // TextureCacheCommon *texcache = gpuDebug->GetTextureCacheCommon(); + + // Let's display the current CLUT. + + } else { + ImGui::Text("Click the buttons above (Tex, etc) to stop"); + } + + ImGui::EndChild(); + ImGui::End(); } From 27122a9e646f6e8da9559e6c4b7257340273b6ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 10 Dec 2024 22:09:51 +0100 Subject: [PATCH 06/58] Work on previews --- Common/GPU/D3D11/thin3d_d3d11.cpp | 25 +++++++++++++++++++++++++ Common/GPU/Vulkan/thin3d_vulkan.cpp | 3 ++- Common/GPU/thin3d.h | 1 + GPU/Common/TextureCacheCommon.h | 2 +- GPU/Vulkan/TextureCacheVulkan.h | 3 ++- UI/ImDebugger/ImGe.cpp | 17 +++++++++++++---- ext/imgui/imgui_impl_thin3d.cpp | 7 ++++++- 7 files changed, 50 insertions(+), 8 deletions(-) diff --git a/Common/GPU/D3D11/thin3d_d3d11.cpp b/Common/GPU/D3D11/thin3d_d3d11.cpp index a911fa80ac95..bc59ace63056 100644 --- a/Common/GPU/D3D11/thin3d_d3d11.cpp +++ b/Common/GPU/D3D11/thin3d_d3d11.cpp @@ -75,6 +75,8 @@ class D3D11Framebuffer : public Framebuffer { colorSRView->Release(); if (depthSRView) depthSRView->Release(); + if (stencilSRView) + stencilSRView->Release(); if (depthStencilTex) depthStencilTex->Release(); if (depthStencilRTView) @@ -85,6 +87,7 @@ class D3D11Framebuffer : public Framebuffer { ID3D11RenderTargetView *colorRTView = nullptr; ID3D11ShaderResourceView *colorSRView = nullptr; ID3D11ShaderResourceView *depthSRView = nullptr; + ID3D11ShaderResourceView *stencilSRView = nullptr; DXGI_FORMAT colorFormat = DXGI_FORMAT_UNKNOWN; ID3D11Texture2D *depthStencilTex = nullptr; @@ -1438,6 +1441,16 @@ void D3D11DrawContext::DrawIndexedClippedBatchUP(const void *vdata, int vertexCo context_->PSSetShaderResources(0, 1, &view); } else { ID3D11ShaderResourceView *view = ((D3D11Framebuffer *)draws[i].bindFramebufferAsTex)->colorSRView; + switch (draws[i].aspect) { + case FB_DEPTH_BIT: + view = ((D3D11Framebuffer *)draws[i].bindFramebufferAsTex)->depthSRView; + break; + case FB_STENCIL_BIT: + view = ((D3D11Framebuffer *)draws[i].bindFramebufferAsTex)->stencilSRView; + break; + default: + break; + } context_->PSSetShaderResources(0, 1, &view); } ID3D11SamplerState *sstate = ((D3D11SamplerState *)draws[i].samplerState)->ss; @@ -1550,6 +1563,18 @@ Framebuffer *D3D11DrawContext::CreateFramebuffer(const FramebufferDesc &desc) { WARN_LOG(Log::G3D, "Failed to create SRV for depth buffer."); fb->depthSRView = nullptr; } + + + D3D11_SHADER_RESOURCE_VIEW_DESC depthStencilViewDesc{}; + depthStencilViewDesc.Format = DXGI_FORMAT_R24G8_TYPELESS; + depthStencilViewDesc.Texture2D.MostDetailedMip = 0; + depthStencilViewDesc.Texture2D.MipLevels = 1; + depthStencilViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + hr = device_->CreateShaderResourceView(fb->depthStencilTex, &depthViewDesc, &fb->stencilSRView); + if (FAILED(hr)) { + WARN_LOG(Log::G3D, "Failed to create SRV for depth+stencil buffer."); + fb->depthSRView = nullptr; + } } return fb; diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index eb7471143299..5302fa030302 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -1599,7 +1599,7 @@ void VKContext::DrawIndexedClippedBatchUP(const void *vdata, int vertexCount, co if (draw.bindTexture) { BindTexture(0, draw.bindTexture); } else if (draw.bindFramebufferAsTex) { - BindFramebufferAsTexture(draw.bindFramebufferAsTex, 0, FBChannel::FB_COLOR_BIT, 0); + BindFramebufferAsTexture(draw.bindFramebufferAsTex, 0, draw.aspect, 0); } else if (draw.bindNativeTexture) { BindNativeTexture(0, draw.bindNativeTexture); } @@ -1834,6 +1834,7 @@ void VKContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChanne aspect = VK_IMAGE_ASPECT_DEPTH_BIT; break; default: + // Hm, can we texture from stencil? _assert_(false); break; } diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index 28fd4a4f8c65..f4826f6b987c 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -708,6 +708,7 @@ struct ClippedDraw { void *bindNativeTexture; Draw::SamplerState *samplerState; Draw::Pipeline *pipeline; + Draw::FBChannel aspect; }; class DrawContext { diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index 7b0c42b272e5..48b29dd35990 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -382,8 +382,8 @@ class TextureCacheCommon { virtual void DrawImGuiDebug(uint64_t &selectedTextureId) const; -protected: virtual void *GetNativeTextureView(const TexCacheEntry *entry, bool flat) const = 0; +protected: bool PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEntry *entry); virtual void BindTexture(TexCacheEntry *entry) = 0; diff --git a/GPU/Vulkan/TextureCacheVulkan.h b/GPU/Vulkan/TextureCacheVulkan.h index a85888222606..13d1f1a6e3fb 100644 --- a/GPU/Vulkan/TextureCacheVulkan.h +++ b/GPU/Vulkan/TextureCacheVulkan.h @@ -83,6 +83,8 @@ class TextureCacheVulkan : public TextureCacheCommon { std::vector DebugGetSamplerIDs() const; std::string DebugGetSamplerString(const std::string &id, DebugShaderStringType stringType); + void *GetNativeTextureView(const TexCacheEntry *entry, bool flat) const override; + protected: void BindTexture(TexCacheEntry *entry) override; void Unbind() override; @@ -90,7 +92,6 @@ class TextureCacheVulkan : public TextureCacheCommon { void BindAsClutTexture(Draw::Texture *tex, bool smooth) override; void ApplySamplingParams(const SamplerCacheKey &key) override; void BoundFramebufferTexture() override; - void *GetNativeTextureView(const TexCacheEntry *entry, bool flat) const override; private: void LoadVulkanTextureLevel(TexCacheEntry &entry, uint8_t *writePtr, int rowPitch, int level, int scaleFactor, VkFormat dstFmt); diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index f71a90f0750f..fe8024d0742b 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -358,12 +358,14 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, GPUDebugInterface *gpuDebug) { } if (ImGui::BeginTabItem("Depth")) { ImTextureID texId = ImGui_ImplThin3d_AddFBAsTextureTemp(vfb->fbo, Draw::FB_DEPTH_BIT, ImGuiPipeline::TexturedOpaque); - ImGui::Image(texId, ImVec2(128, 128)); + ImGui::Image(texId, ImVec2(vfb->width, vfb->height)); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Stencil")) { - ImTextureID texId = ImGui_ImplThin3d_AddFBAsTextureTemp(vfb->fbo, Draw::FB_STENCIL_BIT, ImGuiPipeline::TexturedOpaque); - ImGui::Image(texId, ImVec2(128, 128)); + // Nah, this isn't gonna work. We better just do a readback to texture, but then we need a message and some storage.. + // + //ImTextureID texId = ImGui_ImplThin3d_AddFBAsTextureTemp(vfb->fbo, Draw::FB_STENCIL_BIT, ImGuiPipeline::TexturedOpaque); + //ImGui::Image(texId, ImVec2(vfb->width, vfb->height)); ImGui::EndTabItem(); } ImGui::EndTabBar(); @@ -374,7 +376,14 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, GPUDebugInterface *gpuDebug) { ImGui::Text("Texture: "); - // TextureCacheCommon *texcache = gpuDebug->GetTextureCacheCommon(); + TextureCacheCommon *texcache = gpuDebug->GetTextureCacheCommon(); + TexCacheEntry *tex = texcache->SetTexture(); + texcache->ApplyTexture(); + + void *nativeView = texcache->GetNativeTextureView(tex, true); + ImTextureID texId = ImGui_ImplThin3d_AddNativeTextureTemp(nativeView); + + ImGui::Image(texId, ImVec2(128, 128)); // Let's display the current CLUT. diff --git a/ext/imgui/imgui_impl_thin3d.cpp b/ext/imgui/imgui_impl_thin3d.cpp index ba9da0fd3e7b..430c59fd6deb 100644 --- a/ext/imgui/imgui_impl_thin3d.cpp +++ b/ext/imgui/imgui_impl_thin3d.cpp @@ -89,6 +89,7 @@ void ImGui_ImplThin3d_RenderDrawData(ImDrawData* draw_data, Draw::DrawContext *d void *boundNativeTexture; Draw::Pipeline *boundPipeline = bd->pipelines[0]; Draw::SamplerState *boundSampler = bd->fontSampler; + Draw::FBChannel boundAspect = Draw::FB_COLOR_BIT; // Render command lists for (int n = 0; n < draw_data->CmdListsCount; n++) { @@ -115,16 +116,19 @@ void ImGui_ImplThin3d_RenderDrawData(ImDrawData* draw_data, Draw::DrawContext *d boundFBAsTexture = bd->tempTextures[index].framebuffer; boundTexture = nullptr; boundNativeTexture = nullptr; + boundAspect = bd->tempTextures[index].aspect; break; case RegisteredTextureType::Texture: boundTexture = bd->tempTextures[index].texture; boundFBAsTexture = nullptr; boundNativeTexture = nullptr; + boundAspect = Draw::FB_COLOR_BIT; break; case RegisteredTextureType::NativeTexture: boundTexture = nullptr; boundFBAsTexture = nullptr; - boundNativeTexture = bd->tempTextures[index].nativeTexture;; + boundNativeTexture = bd->tempTextures[index].nativeTexture; + boundAspect = Draw::FB_COLOR_BIT; break; } boundPipeline = bd->pipelines[(int)bd->tempTextures[index].pipeline]; @@ -149,6 +153,7 @@ void ImGui_ImplThin3d_RenderDrawData(ImDrawData* draw_data, Draw::DrawContext *d clippedDraw.bindFramebufferAsTex = boundFBAsTexture; clippedDraw.bindNativeTexture = boundNativeTexture; clippedDraw.samplerState = boundSampler; + clippedDraw.aspect = boundAspect; clippedDraw.clipx = clip_min.x; clippedDraw.clipy = clip_min.y; clippedDraw.clipw = clip_max.x - clip_min.x; From 5817f603466b6d21ccf2de53695dc2ee3cac7886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 10 Dec 2024 22:43:31 +0100 Subject: [PATCH 07/58] Remove redundant code. Add "break after syscall" button, fix up --- Core/HLE/HLE.cpp | 15 +++++++++------ UI/ImDebugger/ImDebugger.cpp | 6 ++++++ Windows/Debugger/Debugger_Disasm.cpp | 5 ----- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Core/HLE/HLE.cpp b/Core/HLE/HLE.cpp index 517fb759ac7f..2e0a3710a3f9 100644 --- a/Core/HLE/HLE.cpp +++ b/Core/HLE/HLE.cpp @@ -371,18 +371,23 @@ void hleCoreTimingForceCheck() { } // Pauses execution after an HLE call. -bool hleExecuteDebugBreak(const HLEFunction &func) -{ +static bool hleExecuteDebugBreak(const HLEFunction *func) { + if (!func || coreState == CORE_RUNNING_GE) { + // Let's break on the next one. + return false; + } + const u32 NID_SUSPEND_INTR = 0x092968F4, NID_RESUME_INTR = 0x5F10D406; // Never break on these, they're noise. u32 blacklistedNIDs[] = {NID_SUSPEND_INTR, NID_RESUME_INTR, NID_IDLE}; for (size_t i = 0; i < ARRAY_SIZE(blacklistedNIDs); ++i) { - if (func.ID == blacklistedNIDs[i]) + if (func->ID == blacklistedNIDs[i]) return false; } + INFO_LOG(Log::CPU, "Broke after syscall: %s", func->name); Core_Break("hle.step", latestSyscallPC); return true; } @@ -624,9 +629,7 @@ static void hleFinishSyscall(const HLEFunction *info) { __KernelReSchedule(hleAfterSyscallReschedReason); if ((hleAfterSyscall & HLE_AFTER_DEBUG_BREAK) != 0) { - _dbg_assert_(info); - if (!hleExecuteDebugBreak(*info)) - { + if (!hleExecuteDebugBreak(info)) { // We'll do it next syscall. hleAfterSyscall = HLE_AFTER_DEBUG_BREAK; hleAfterSyscallReschedReason = 0; diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index e115c6643edd..a317d17c5100 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -1115,6 +1115,12 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, CoreStat Core_RequestCPUStep(CPUStepType::Frame, 0); } + ImGui::SameLine(); + if (ImGui::SmallButton("Syscall")) { + hleDebugBreak(); + Core_Resume(); + } + ImGui::SameLine(); ImGui::SmallButton("Skim"); if (ImGui::IsItemActive()) { diff --git a/Windows/Debugger/Debugger_Disasm.cpp b/Windows/Debugger/Debugger_Disasm.cpp index 46224da48704..2711c990b1a2 100644 --- a/Windows/Debugger/Debugger_Disasm.cpp +++ b/Windows/Debugger/Debugger_Disasm.cpp @@ -405,8 +405,6 @@ BOOL CDisasm::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { } else { // go lastTicks_ = CoreTiming::GetTicks(); - // If the current PC is on a breakpoint, the user doesn't want to do nothing. - breakpoints_->SetSkipFirst(currentMIPS->pc); Core_Resume(); } } @@ -430,9 +428,6 @@ BOOL CDisasm::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { break; lastTicks_ = CoreTiming::GetTicks(); - // If the current PC is on a breakpoint, the user doesn't want to do nothing. - breakpoints_->SetSkipFirst(currentMIPS->pc); - hleDebugBreak(); Core_Resume(); } From 637d15434e88597a0f7c9e5afc51712628c1ba2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 10 Dec 2024 22:56:03 +0100 Subject: [PATCH 08/58] Minor code cleanup. Add Goto LR button --- Core/Debugger/DebugInterface.h | 2 -- Core/HLE/KernelThreadDebugInterface.h | 3 --- Core/MIPS/MIPSDebugInterface.h | 2 -- UI/ImDebugger/ImDebugger.cpp | 4 ++++ UI/ImDebugger/ImDisasmView.cpp | 11 +++++++---- UI/ImDebugger/ImDisasmView.h | 7 +++++-- Windows/Debugger/CtrlDisAsmView.cpp | 8 ++++---- Windows/Debugger/CtrlDisAsmView.h | 4 ++-- 8 files changed, 22 insertions(+), 19 deletions(-) diff --git a/Core/Debugger/DebugInterface.h b/Core/Debugger/DebugInterface.h index 8d71fc78191a..0c82696654b7 100644 --- a/Core/Debugger/DebugInterface.h +++ b/Core/Debugger/DebugInterface.h @@ -35,8 +35,6 @@ class DebugInterface { virtual void clearAllBreakpoints() = 0; virtual void toggleBreakpoint(unsigned int address) = 0; virtual unsigned int readMemory(unsigned int address) {return 0;} - virtual unsigned int getPC() = 0; - virtual void setPC(unsigned int address) {} virtual void step() {} virtual void runToBreakpoint() {} virtual int getColor(unsigned int address, bool darkMode) const {return darkMode ? 0xFF101010 : 0xFFFFFFFF;} diff --git a/Core/HLE/KernelThreadDebugInterface.h b/Core/HLE/KernelThreadDebugInterface.h index 41260ad7c05b..eb3c754675ac 100644 --- a/Core/HLE/KernelThreadDebugInterface.h +++ b/Core/HLE/KernelThreadDebugInterface.h @@ -26,9 +26,6 @@ class KernelThreadDebugInterface : public MIPSDebugInterface { KernelThreadDebugInterface(MIPSState *c, PSPThreadContext &t) : MIPSDebugInterface(c), ctx(t) { } - unsigned int getPC() override { return ctx.pc; } - void setPC(unsigned int address) override { ctx.pc = address; } - u32 GetGPR32Value(int reg) override { return ctx.r[reg]; } u32 GetPC() override { return ctx.pc; } u32 GetLR() override { return ctx.r[MIPS_REG_RA]; } diff --git a/Core/MIPS/MIPSDebugInterface.h b/Core/MIPS/MIPSDebugInterface.h index 1ec6bbc01888..ac62511af03a 100644 --- a/Core/MIPS/MIPSDebugInterface.h +++ b/Core/MIPS/MIPSDebugInterface.h @@ -36,8 +36,6 @@ class MIPSDebugInterface : public DebugInterface void clearAllBreakpoints() override; void toggleBreakpoint(unsigned int address) override; unsigned int readMemory(unsigned int address) override; - unsigned int getPC() override { return cpu->pc; } - void setPC(unsigned int address) override { cpu->pc = address; } void step() override {} void runToBreakpoint() override; int getColor(unsigned int address, bool darkMode) const override; diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index a317d17c5100..fab9a455bb81 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -1134,6 +1134,10 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, CoreStat if (ImGui::SmallButton("Goto PC")) { disasmView_.gotoPC(); } + ImGui::SameLine(); + if (ImGui::SmallButton("Goto LR")) { + disasmView_.gotoLR(); + } if (ImGui::BeginPopup("disSearch")) { if (ImGui::IsWindowAppearing()) { diff --git a/UI/ImDebugger/ImDisasmView.cpp b/UI/ImDebugger/ImDisasmView.cpp index a3cd4f051ab7..48d1a123ceda 100644 --- a/UI/ImDebugger/ImDisasmView.cpp +++ b/UI/ImDebugger/ImDisasmView.cpp @@ -363,6 +363,9 @@ void ImDisasmView::Draw(ImDrawList *drawList) { const std::set currentArguments = getSelectedLineArguments(); DisassemblyLineInfo line; + + const u32 pc = debugger->GetPC(); + for (int i = 0; i < visibleRows_; i++) { manager.getLine(address, displaySymbols_, line); @@ -375,7 +378,7 @@ void ImDisasmView::Draw(ImDrawList *drawList) { ImColor backgroundColor = ImColor(0xFF000000 | debugger->getColor(address, true)); ImColor textColor = 0xFFFFFFFF; - if (isInInterval(address, line.totalSize, debugger->getPC())) { + if (isInInterval(address, line.totalSize, pc)) { backgroundColor = scaleColor(backgroundColor, 1.3f); } @@ -404,7 +407,7 @@ void ImDisasmView::Draw(ImDrawList *drawList) { getDisasmAddressText(address, addressText, true, line.type == DISTYPE_OPCODE); drawList->AddText(ImVec2(rect.left + pixelPositions_.addressStart, rect.top + rowY1 + 2), textColor, addressText); - if (isInInterval(address, line.totalSize, debugger->getPC())) { + if (isInInterval(address, line.totalSize, pc)) { // Show the current PC with a little triangle. drawList->AddTriangleFilled( ImVec2(canvas_p0.x + pixelPositions_.opcodeStart - rowHeight_ * 0.7f, canvas_p0.y + rowY1 + 2), @@ -414,7 +417,7 @@ void ImDisasmView::Draw(ImDrawList *drawList) { } // display whether the condition of a branch is met - if (line.info.isConditional && address == debugger->getPC()) { + if (line.info.isConditional && address == pc) { line.params += line.info.conditionMet ? " ; true" : " ; false"; } @@ -809,7 +812,7 @@ void ImDisasmView::PopupMenu() { ImGui::Separator(); if (ImGui::MenuItem("Set PC to here")) { - debugger->setPC(curAddress_); + debugger->SetPC(curAddress_); } if (ImGui::MenuItem("Follow branch")) { FollowBranch(); diff --git a/UI/ImDebugger/ImDisasmView.h b/UI/ImDebugger/ImDisasmView.h index 592636720942..00906c45cbea 100644 --- a/UI/ImDebugger/ImDisasmView.h +++ b/UI/ImDebugger/ImDisasmView.h @@ -45,7 +45,7 @@ class ImDisasmView { void setDebugger(DebugInterface *deb) { if (debugger != deb) { debugger = deb; - curAddress_ = debugger->getPC(); + curAddress_ = debugger->GetPC(); manager.setCpu(deb); } } @@ -70,7 +70,10 @@ class ImDisasmView { ScanVisibleFunctions(); } void gotoPC() { - gotoAddr(debugger->getPC()); + gotoAddr(debugger->GetPC()); + } + void gotoLR() { + gotoAddr(debugger->GetLR()); } u32 getSelection() { return curAddress_; diff --git a/Windows/Debugger/CtrlDisAsmView.cpp b/Windows/Debugger/CtrlDisAsmView.cpp index 87929268df26..cfc3ad04a842 100644 --- a/Windows/Debugger/CtrlDisAsmView.cpp +++ b/Windows/Debugger/CtrlDisAsmView.cpp @@ -531,7 +531,7 @@ void CtrlDisAsmView::onPaint(WPARAM wParam, LPARAM lParam) COLORREF backgroundColor = whiteBackground ? 0xFFFFFF : (debugger->getColor(address, false) & 0xFFFFFF); COLORREF textColor = 0x000000; - if (isInInterval(address, line.totalSize, debugger->getPC())) + if (isInInterval(address, line.totalSize, debugger->GetPC())) { backgroundColor = scaleColor(backgroundColor,1.05f); } @@ -574,13 +574,13 @@ void CtrlDisAsmView::onPaint(WPARAM wParam, LPARAM lParam) getDisasmAddressText(address,addressText,true,line.type == DISTYPE_OPCODE); TextOutA(hdc,pixelPositions.addressStart,rowY1+2,addressText,(int)strlen(addressText)); - if (isInInterval(address,line.totalSize,debugger->getPC())) + if (isInInterval(address,line.totalSize,debugger->GetPC())) { TextOut(hdc,pixelPositions.opcodeStart-8,rowY1,L"\x25A0",1); } // display whether the condition of a branch is met - if (line.info.isConditional && address == debugger->getPC()) + if (line.info.isConditional && address == debugger->GetPC()) { line.params += line.info.conditionMet ? " ; true" : " ; false"; } @@ -1009,7 +1009,7 @@ void CtrlDisAsmView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) } break; case ID_DISASM_SETPCTOHERE: - debugger->setPC(curAddress); + debugger->SetPC(curAddress); redraw(); break; case ID_DISASM_FOLLOWBRANCH: diff --git a/Windows/Debugger/CtrlDisAsmView.h b/Windows/Debugger/CtrlDisAsmView.h index fd6fcf3907de..b217f6746d39 100644 --- a/Windows/Debugger/CtrlDisAsmView.h +++ b/Windows/Debugger/CtrlDisAsmView.h @@ -111,7 +111,7 @@ class CtrlDisAsmView void setDebugger(DebugInterface *deb) { debugger=deb; - curAddress=debugger->getPC(); + curAddress=debugger->GetPC(); manager.setCpu(deb); } DebugInterface *getDebugger() @@ -140,7 +140,7 @@ class CtrlDisAsmView } void gotoPC() { - gotoAddr(debugger->getPC()); + gotoAddr(debugger->GetPC()); } u32 getSelection() { From 0973fea7ce2413054614a1bce13913a0f6995574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Dec 2024 09:27:29 +0100 Subject: [PATCH 09/58] Add error message if compat.ini can't be found --- Core/Compatibility.cpp | 6 ++++++ Tools/langtool/{unused-euristic.sh => unused-heuristic.sh} | 0 assets/lang/ar_AE.ini | 1 + assets/lang/az_AZ.ini | 1 + assets/lang/bg_BG.ini | 1 + assets/lang/ca_ES.ini | 1 + assets/lang/cz_CZ.ini | 1 + assets/lang/da_DK.ini | 1 + assets/lang/de_DE.ini | 1 + assets/lang/dr_ID.ini | 1 + assets/lang/en_US.ini | 1 + assets/lang/es_ES.ini | 1 + assets/lang/es_LA.ini | 1 + assets/lang/fa_IR.ini | 1 + assets/lang/fi_FI.ini | 1 + assets/lang/fr_FR.ini | 1 + assets/lang/gl_ES.ini | 1 + assets/lang/gr_EL.ini | 1 + assets/lang/he_IL.ini | 1 + assets/lang/he_IL_invert.ini | 1 + assets/lang/hr_HR.ini | 1 + assets/lang/hu_HU.ini | 1 + assets/lang/id_ID.ini | 1 + assets/lang/it_IT.ini | 1 + assets/lang/ja_JP.ini | 1 + assets/lang/jv_ID.ini | 1 + assets/lang/ko_KR.ini | 1 + assets/lang/ku_SO.ini | 1 + assets/lang/lo_LA.ini | 1 + assets/lang/lt-LT.ini | 1 + assets/lang/ms_MY.ini | 1 + assets/lang/nl_NL.ini | 1 + assets/lang/no_NO.ini | 1 + assets/lang/pl_PL.ini | 1 + assets/lang/pt_BR.ini | 1 + assets/lang/pt_PT.ini | 1 + assets/lang/ro_RO.ini | 1 + assets/lang/ru_RU.ini | 1 + assets/lang/sv_SE.ini | 1 + assets/lang/tg_PH.ini | 1 + assets/lang/th_TH.ini | 1 + assets/lang/tr_TR.ini | 1 + assets/lang/uk_UA.ini | 1 + assets/lang/vi_VN.ini | 1 + assets/lang/zh_CN.ini | 1 + assets/lang/zh_TW.ini | 1 + 46 files changed, 50 insertions(+) rename Tools/langtool/{unused-euristic.sh => unused-heuristic.sh} (100%) diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index d651ccfd4c58..379042198011 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -19,8 +19,10 @@ #include "Common/Log.h" #include "Common/Data/Format/IniFile.h" +#include "Common/Data/Text/I18n.h" #include "Common/File/VFS/VFS.h" #include "Common/StringUtils.h" +#include "Common/System/OSD.h" #include "Core/Compatibility.h" #include "Core/Config.h" #include "Core/System.h" @@ -41,6 +43,10 @@ void Compatibility::Load(const std::string &gameID) { // This loads from assets. if (compat.LoadFromVFS(g_VFS, "compat.ini")) { CheckSettings(compat, gameID); + } else { + auto e = GetI18NCategory(I18NCat::ERRORS); + std::string msg = ApplySafeSubstitutions(e->T("File not found: %1"), "compat.ini"); + g_OSD.Show(OSDType::MESSAGE_ERROR, msg, 3.0f); } } diff --git a/Tools/langtool/unused-euristic.sh b/Tools/langtool/unused-heuristic.sh similarity index 100% rename from Tools/langtool/unused-euristic.sh rename to Tools/langtool/unused-heuristic.sh diff --git a/assets/lang/ar_AE.ini b/assets/lang/ar_AE.ini index a0407ea60112..85cf22093837 100644 --- a/assets/lang/ar_AE.ini +++ b/assets/lang/ar_AE.ini @@ -487,6 +487,7 @@ Error reading file = ‎خطأ في قرأة الملف. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/az_AZ.ini b/assets/lang/az_AZ.ini index 756c6fd5bd76..750a83cd5c47 100644 --- a/assets/lang/az_AZ.ini +++ b/assets/lang/az_AZ.ini @@ -479,6 +479,7 @@ Error reading file = error reading file. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/bg_BG.ini b/assets/lang/bg_BG.ini index 129e22681374..f775693c1bc4 100644 --- a/assets/lang/bg_BG.ini +++ b/assets/lang/bg_BG.ini @@ -479,6 +479,7 @@ Error reading file = Грешка при четенето на файла. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/ca_ES.ini b/assets/lang/ca_ES.ini index ecbc283bd306..da4318cb6563 100644 --- a/assets/lang/ca_ES.ini +++ b/assets/lang/ca_ES.ini @@ -479,6 +479,7 @@ Error reading file = error reading file. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/cz_CZ.ini b/assets/lang/cz_CZ.ini index 948765a4827b..4d41866114bb 100644 --- a/assets/lang/cz_CZ.ini +++ b/assets/lang/cz_CZ.ini @@ -479,6 +479,7 @@ Error reading file = Chyba při čtení souboru. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/da_DK.ini b/assets/lang/da_DK.ini index a348dcd9a0f3..35f0add41ef8 100644 --- a/assets/lang/da_DK.ini +++ b/assets/lang/da_DK.ini @@ -479,6 +479,7 @@ Error reading file = Fejl ved læsning af fil. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/de_DE.ini b/assets/lang/de_DE.ini index d57ac6d84bbc..2b7ee9af64c4 100644 --- a/assets/lang/de_DE.ini +++ b/assets/lang/de_DE.ini @@ -479,6 +479,7 @@ Error reading file = Fehler beim Lesen der Datei. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Laden der Programmdatei fehlgeschlagen: File corrupt = Datei beschädigt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Fehlerhaftes einlesen der Spieldisk: ISO fehlerhaft. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/dr_ID.ini b/assets/lang/dr_ID.ini index 8a44c90b10f3..16ec96a36199 100644 --- a/assets/lang/dr_ID.ini +++ b/assets/lang/dr_ID.ini @@ -479,6 +479,7 @@ Error reading file = erormi te' fail mane. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index 9a18c4129030..163da2dfefb0 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -503,6 +503,7 @@ Error reading file = Error reading file. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/es_ES.ini b/assets/lang/es_ES.ini index 1a46d2320e93..e1f2960f87f8 100644 --- a/assets/lang/es_ES.ini +++ b/assets/lang/es_ES.ini @@ -479,6 +479,7 @@ Error reading file = Error al leer el archivo. Failed initializing CPU/Memory = Fallo inicializando CPU o memoria. Failed to load executable: = Fallo al cargar el ejecutable: File corrupt = Archivo corrupto. +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Error de lectura: ISO corrupta. GenericAllStartupError = Error al iniciar los gráficos. Prueba a actualizar tu controlador gráfico. GenericBackendSwitchCrash = PPSSPP falló durante la inicialización de los gráficos. Prueba a actualizar los controladores gráficos.\n\nSe ha cambiado el motor gráfico: diff --git a/assets/lang/es_LA.ini b/assets/lang/es_LA.ini index 8e34a144e7bf..57be2f4adf11 100644 --- a/assets/lang/es_LA.ini +++ b/assets/lang/es_LA.ini @@ -479,6 +479,7 @@ Error reading file = Error al leer el archivo. Failed initializing CPU/Memory = Falló al iniciar CPU o memoria. Failed to load executable: = Falló al cargar ejecutable: File corrupt = Archivo corrupto. +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Falló al cargar disco de juego: ISO corrupta. GenericAllStartupError = Fallo al iniciar los gráficos de cualquier API. Intenta actualizar los drivers de los gráficos y de otros. GenericBackendSwitchCrash = Fallo al iniciar los gráficos. Intenta actualizar los drivers de los gráficos.\n\nLa API de los gráficos ha sido cambiado: diff --git a/assets/lang/fa_IR.ini b/assets/lang/fa_IR.ini index 4251ad62187d..1356c1b6b86b 100644 --- a/assets/lang/fa_IR.ini +++ b/assets/lang/fa_IR.ini @@ -479,6 +479,7 @@ Error reading file = ‎.خطا در خواندن فایل Failed initializing CPU/Memory = خطا در نصب CPU و حافظه Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/fi_FI.ini b/assets/lang/fi_FI.ini index 0163d83d1219..3bd194b0f39c 100644 --- a/assets/lang/fi_FI.ini +++ b/assets/lang/fi_FI.ini @@ -479,6 +479,7 @@ Error reading file = error reading file. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/fr_FR.ini b/assets/lang/fr_FR.ini index 4c57c60a66fd..8ede81b025b1 100644 --- a/assets/lang/fr_FR.ini +++ b/assets/lang/fr_FR.ini @@ -479,6 +479,7 @@ Error reading file = Erreur lors de la lecture du fichier. Failed initializing CPU/Memory = Échec d'initialisation du CPU ou de la mémoire Failed to load executable: = Le chargement de l'exécutable a échoué : File corrupt = Fichier corrompu +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Erreur de lecture du disque de jeu : ISO corrompu. GenericAllStartupError = Échec du démarrage de PPSSPP avec tous les back-ends graphiques.\n\nEssayez de mettre à jour les pilotes graphiques et autres. GenericBackendSwitchCrash = Plantage lors du démarrage de PPSSPP.\n\nC'est généralement dû à un problème de pilotes graphiques. Essayez de mettre à jour les pilotes graphiques.\n\nLe back-end graphique a été changé : diff --git a/assets/lang/gl_ES.ini b/assets/lang/gl_ES.ini index bb44ac584e76..9d99465040a7 100644 --- a/assets/lang/gl_ES.ini +++ b/assets/lang/gl_ES.ini @@ -479,6 +479,7 @@ Error reading file = Erro o ler o arquivo Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/gr_EL.ini b/assets/lang/gr_EL.ini index d2ea776c31eb..076c4a3dae45 100644 --- a/assets/lang/gr_EL.ini +++ b/assets/lang/gr_EL.ini @@ -479,6 +479,7 @@ Error reading file = Σφάλμα ανάγνωσης αρχείου. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Αδυναμιά φόρτωσης εκτελέσιμου αρχείου: File corrupt = Κατεστραμμένο αρχείο +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Σφάλμα ανάγνωσης δίσκου: κατεστραμμένο ISO. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/he_IL.ini b/assets/lang/he_IL.ini index ef31696eb5f9..604bf7fc9ea4 100644 --- a/assets/lang/he_IL.ini +++ b/assets/lang/he_IL.ini @@ -479,6 +479,7 @@ Error reading file = קריאת קובץ נכשלה. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/he_IL_invert.ini b/assets/lang/he_IL_invert.ini index 2b57a897c12e..6945c4f453ef 100644 --- a/assets/lang/he_IL_invert.ini +++ b/assets/lang/he_IL_invert.ini @@ -479,6 +479,7 @@ Error reading file = .הלשכנ ץבוק תאירק Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/hr_HR.ini b/assets/lang/hr_HR.ini index 5b616d5b5e36..c624c152d057 100644 --- a/assets/lang/hr_HR.ini +++ b/assets/lang/hr_HR.ini @@ -479,6 +479,7 @@ Error reading file = Greška tijekom čitanja datoteke. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Greška u čitanju slike diska: ISO korumpiran. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/hu_HU.ini b/assets/lang/hu_HU.ini index 6f59c97ddfc8..d0f32c87fa85 100644 --- a/assets/lang/hu_HU.ini +++ b/assets/lang/hu_HU.ini @@ -479,6 +479,7 @@ Error reading file = Fájl olvasási hiba. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = A futtatható állomány betöltése sikertelen: File corrupt = A fájl sérült +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Játék lemez olvasási hiba: sérült ISO. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/id_ID.ini b/assets/lang/id_ID.ini index 7140b634d7e2..3d3476550053 100644 --- a/assets/lang/id_ID.ini +++ b/assets/lang/id_ID.ini @@ -479,6 +479,7 @@ Error reading file = Kesalahan saat membaca berkas. Failed initializing CPU/Memory = Gagal menginisialisasi CPU atau memori Failed to load executable: = Gagal memuat yang dapat dieksekusi: File corrupt = Berkas rusak +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Kesalahan pembacaan disk permainan: ISO rusak. GenericAllStartupError = PPSSPP gagal memulai dengan penyangga grafis apa pun. Coba perbarui grafis Anda dan driver lainnya GenericBackendSwitchCrash = PPSSPP mogok saat memulai.\n\nIni biasanya berarti masalah driver grafis. Coba tingkatkan driver grafis Anda.\n\nPenyangga grafis telah dialihkan: diff --git a/assets/lang/it_IT.ini b/assets/lang/it_IT.ini index d1851a79b5f1..fc2fa08d23b4 100644 --- a/assets/lang/it_IT.ini +++ b/assets/lang/it_IT.ini @@ -479,6 +479,7 @@ Error reading file = Errore nella Lettura del File. Failed initializing CPU/Memory = Errore di inizializzazione della CPU o della memoria Failed to load executable: = Caricamento dell'eseguibile fallito: File corrupt = File corrotto +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Errore di lettura disco del gioco: ISO corrotta. GenericAllStartupError = PPSSPP non è stato in grado di avviare tutti i backend grafici. Si consiglia di provare ad aggiornare i driver della scheda grafica. GenericBackendSwitchCrash = PPSSPP ha crashato all'avvio.\n\nCiò è solitamente dovuto a un problema con i driver grafici. Prova ad aggiornare i driver della scheda grafica.\n\nI backend grafici sono stati cambiati: diff --git a/assets/lang/ja_JP.ini b/assets/lang/ja_JP.ini index 9da195bad1ee..9b4e545a58a0 100644 --- a/assets/lang/ja_JP.ini +++ b/assets/lang/ja_JP.ini @@ -479,6 +479,7 @@ Error reading file = ファイルを読み込めません。 Failed initializing CPU/Memory = CPUメモリが初期化できません。 Failed to load executable: = 実行ファイルを読み込めません: File corrupt = ファイルが壊れています +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = ゲームディスクを読み込めません: ISOが壊れています GenericAllStartupError = PPSSPPは起動時にクラッシュしました。 可能な場合はグラフィックスドライバーの更新をしてください。 GenericBackendSwitchCrash = PPSSPPは起動時にクラッシュしました。\n\n グラフィックスドライバーの問題が考えられます。 可能な場合はグラフィックスドライバーの更新をしてください。\n\n グラフィックスバックエンドを変更しました: diff --git a/assets/lang/jv_ID.ini b/assets/lang/jv_ID.ini index 8affd07807f3..73ab68c44d76 100644 --- a/assets/lang/jv_ID.ini +++ b/assets/lang/jv_ID.ini @@ -479,6 +479,7 @@ Error reading file = Galat nalika maca berkas. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/ko_KR.ini b/assets/lang/ko_KR.ini index 710673b5bf04..64a39e77a4b1 100644 --- a/assets/lang/ko_KR.ini +++ b/assets/lang/ko_KR.ini @@ -479,6 +479,7 @@ Error reading file = 파일 읽기 오류입니다. Failed initializing CPU/Memory = CPU 또는 메모리 초기화 실패 Failed to load executable: = 실행 파일 불러오기 실패: File corrupt = 파일 손상 +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = 게임 디스크 읽기 오류: ISO 손상되었습니다. GenericAllStartupError = PPSSPP가 그래픽 백엔드로 시작하지 못했습니다. 그래픽 및 기타 드라이버를 업그레이드해 보세요. GenericBackendSwitchCrash = 시작하는 동안 PPSSPP가 충돌했습니다.\n\n이것은 일반적으로 그래픽 드라이버 문제를 의미합니다. 그래픽 드라이버를 업그레이드해 보세요.\n\n그래픽 백엔드가 전환되었습니다: diff --git a/assets/lang/ku_SO.ini b/assets/lang/ku_SO.ini index 92dbde71ddd6..ccbaf9e52ced 100644 --- a/assets/lang/ku_SO.ini +++ b/assets/lang/ku_SO.ini @@ -493,6 +493,7 @@ Error reading file = Error reading file. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/lo_LA.ini b/assets/lang/lo_LA.ini index ae5482fcf393..437d71652b19 100644 --- a/assets/lang/lo_LA.ini +++ b/assets/lang/lo_LA.ini @@ -479,6 +479,7 @@ Error reading file = ເກີດຂໍ້ຜິດພາດໃນການອ Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/lt-LT.ini b/assets/lang/lt-LT.ini index 0c2f5200ed8c..c5a8a9dc4663 100644 --- a/assets/lang/lt-LT.ini +++ b/assets/lang/lt-LT.ini @@ -479,6 +479,7 @@ Error reading file = Klaida skaitant failą. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/ms_MY.ini b/assets/lang/ms_MY.ini index 3234b6633851..63def4a651f5 100644 --- a/assets/lang/ms_MY.ini +++ b/assets/lang/ms_MY.ini @@ -479,6 +479,7 @@ Error reading file = Pembacaan fail bermasalah. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/nl_NL.ini b/assets/lang/nl_NL.ini index 0937772a059e..8585c951af2a 100644 --- a/assets/lang/nl_NL.ini +++ b/assets/lang/nl_NL.ini @@ -479,6 +479,7 @@ Error reading file = Kan het bestand niet lezen. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/no_NO.ini b/assets/lang/no_NO.ini index c1f6382bf763..818535fd90f8 100644 --- a/assets/lang/no_NO.ini +++ b/assets/lang/no_NO.ini @@ -479,6 +479,7 @@ Error reading file = error reading file. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/pl_PL.ini b/assets/lang/pl_PL.ini index c8ab1ac8d076..19ab5439fe3a 100644 --- a/assets/lang/pl_PL.ini +++ b/assets/lang/pl_PL.ini @@ -479,6 +479,7 @@ Error reading file = Błąd podczas odczytywania pliku. Failed initializing CPU/Memory = Nie można zainicjować CPU lub pamięci Failed to load executable: = Nie udało się uruchomić pliku wykonywalnego: File corrupt = Uszkodzony plik +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Nie można odczytać danych z dysku - plik ISO uszkodzony. GenericAllStartupError = PPSSPP nie może uruchomić żadnego trybu grafiki. Spróbuj zaktualizować swoje sterowniki do karty graficznej (oraz pozostałe). GenericBackendSwitchCrash = PPSSPP - wystąpił błąd podczas uruchamiania emulatora.\n\nMoże to oznaczać problem ze sterownikiem do karty graficznej. Spróbuj go zaktualizować.\n\nSilnik graficzny został zmieniony: diff --git a/assets/lang/pt_BR.ini b/assets/lang/pt_BR.ini index 1d87195fea08..c22452860f7b 100644 --- a/assets/lang/pt_BR.ini +++ b/assets/lang/pt_BR.ini @@ -503,6 +503,7 @@ Error reading file = Erro ao ler o arquivo. Failed initializing CPU/Memory = Falhou em inicializar a CPU ou a memória Failed to load executable: = Falhou em carregar o executável: File corrupt = Arquivo corrompido +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Erro de leitura do disco do jogo: ISO corrompida. GenericAllStartupError = O PPSSPP falhou em iniciar com qualquer backend gráfico. Tente atualizar seus gráficos e outros drivers. GenericBackendSwitchCrash = O PPSSPP teve um crash enquanto iniciava.\n\nIsto geralmente significa um problema do driver gráfico. Tente atualizar seus drivers gráficos.\n\nO backend dos gráficos foi trocado: diff --git a/assets/lang/pt_PT.ini b/assets/lang/pt_PT.ini index d6a885254358..5908af3274ac 100644 --- a/assets/lang/pt_PT.ini +++ b/assets/lang/pt_PT.ini @@ -503,6 +503,7 @@ Error reading file = Erro ao ler o ficheiro. Failed initializing CPU/Memory = Erro ao inicializar a CPU/memória. Failed to load executable: = Erro ao carregar o executável: File corrupt = Ficheiro Corrompido +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Erro de leitura do disco do jogo: ISO corrompida. GenericAllStartupError = O PPSSPP falhou em iniciar com todos backends gráficos disponíveis. Tenta atualizar os seus gráficos e outros drivers. GenericBackendSwitchCrash = O PPSSPP crashou enquanto iniciava.\n\nIsto geralmente significa que há um problema no driver dos gráficos. Tenta atualizar os drivers.\n\nO backend dos gráficos foi trocado: diff --git a/assets/lang/ro_RO.ini b/assets/lang/ro_RO.ini index 8bd98f16408b..def46f8c3ee5 100644 --- a/assets/lang/ro_RO.ini +++ b/assets/lang/ro_RO.ini @@ -480,6 +480,7 @@ Error reading file = Eroare la citire fișier. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Game disc read error: ISO corrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/ru_RU.ini b/assets/lang/ru_RU.ini index ca949e4d40b4..237b8371f68f 100644 --- a/assets/lang/ru_RU.ini +++ b/assets/lang/ru_RU.ini @@ -479,6 +479,7 @@ Error reading file = Ошибка при чтении файла. Failed initializing CPU/Memory = Не удалось инициализировать ЦП или память Failed to load executable: = не удалось запустить: File corrupt = файл поврежден +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Ошибка чтения игрового диска: ISO повреждён. GenericAllStartupError = Не удалось запустить PPSSPP ни с одним графическим бэкендом. Попробуйте обновить графические и другие драйвера. GenericBackendSwitchCrash = При запуске PPSSPP произошла ошибка.\n\nОбычно это происходит из-за проблем с графическим драйвером. Попробуйте обновить графические драйвера.\n\nГрафический бэкенд был изменен: diff --git a/assets/lang/sv_SE.ini b/assets/lang/sv_SE.ini index ded1ceaf0927..565586275889 100644 --- a/assets/lang/sv_SE.ini +++ b/assets/lang/sv_SE.ini @@ -480,6 +480,7 @@ Error reading file = Fel vid filläsning. Failed initializing CPU/Memory = Misslyckades initiera CPU eller memory Failed to load executable: = Misslyckades ladda executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Läsfel: ISO korrupt. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/tg_PH.ini b/assets/lang/tg_PH.ini index 49a9735bf418..f5993c06ac30 100644 --- a/assets/lang/tg_PH.ini +++ b/assets/lang/tg_PH.ini @@ -480,6 +480,7 @@ Error reading file = Mali sa pagbasa ng file Failed initializing CPU/Memory = Bigong i-initialize ang CPU o memory Failed to load executable: = Bigong i-load ang executable: File corrupt = Sira ang file +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Error sa pagbabasa ng disc ng laro: Nasira ang ISO. GenericAllStartupError = Nabigo ang PPSSPP na magsimula gamit ang anumang graphics backend. Subukang i-upgrade ang iyong mga graphics at iba pang mga driver. GenericBackendSwitchCrash = Nag-crash ang PPSSPP habang nagsisimula.\nn Karaniwang nangangahulugan ito ng problema sa driver ng graphics. Subukang i-upgrade ang iyong mga graphics driver.\nn\Graphics backend ay inilipat na: diff --git a/assets/lang/th_TH.ini b/assets/lang/th_TH.ini index 0d00db444de2..c42e57acaf1c 100644 --- a/assets/lang/th_TH.ini +++ b/assets/lang/th_TH.ini @@ -489,6 +489,7 @@ Error reading file = เกิดข้อผิดพลาดในการ Failed initializing CPU/Memory = ล้มเหลวในช่วงเริ่มต้นการทำงานของซีพียู หรือหน่วยความจำ Failed to load executable: = ล้มเหลวในการโหลดไฟล์ปฏิบัติการ: File corrupt = ไฟล์เสียหาย +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = ไม่สามารถอ่านตัวเกมได้ - ไฟล์ ISO เสียหาย GenericAllStartupError = PPSSPP ไม่สามารถเริ่มต้นทำงานด้วยรูปแบบการสนับสนุนกราฟิกใด ๆ ให้ลองอัพเดทไดรเวอร์การ์ดจอ และไดรเวอร์อื่น ๆ ดูก่อน GenericBackendSwitchCrash = PPSSPP หยุดการทำงานขณะเริ่ม\n\nซึ่งสาเหตุหลักๆ อาจมาจากปัญหาไดรเวอร์ ให้ลองอัพเดทไดรเวอร์การ์ดจอดูก่อน\n\nสลับไปใช้รูปแบบการสนับสนุนกราฟิก: diff --git a/assets/lang/tr_TR.ini b/assets/lang/tr_TR.ini index 316d3235e540..ea7f1737e990 100644 --- a/assets/lang/tr_TR.ini +++ b/assets/lang/tr_TR.ini @@ -481,6 +481,7 @@ Error reading file = Dosya okumada hata. Failed initializing CPU/Memory = CPU veya bellek başlatılamadı Failed to load executable: = Yürütülebilir dosya yüklenemedi: File corrupt = Bozuk dosya +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Oyun diskini okurken hata oluştu: ISO bozuk. GenericAllStartupError = PPSSPP, herhangi bir grafik arkaucuyla başlatılamadı. Ekran kartınızı ve diğer sürücüleri yükseltmeyi deneyin. GenericBackendSwitchCrash = PPSSPP başlatılırken çöktü.\n\n Bu genellikle bir ekran kartı sürücüsü sorunu anlamına gelir. Sürücüyü güncellemeyi deneyin.\n\nGrafik arka ucu değiştirildi: diff --git a/assets/lang/uk_UA.ini b/assets/lang/uk_UA.ini index e2c119f9f2ce..0f5527bcaf26 100644 --- a/assets/lang/uk_UA.ini +++ b/assets/lang/uk_UA.ini @@ -479,6 +479,7 @@ Error reading file = Помилка при читанні файлу. Failed initializing CPU/Memory = Невдала ініціалізація процесора/пам'яті Failed to load executable: = Не вдалось завантажити: File corrupt = Файл пошкоджений +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Помилка читання ігрового диску: ISO пошкоджено. GenericAllStartupError = PPSSPP не вдалося запустити з жодним графічним бекендом. Спробуйте оновити графічні та інші драйвери. GenericBackendSwitchCrash = PPSSPP вийшов з ладу під час запуску.\n\nЗазвичай це означає проблему з графічним драйвером. Спробуйте оновити драйвери відеокарти.\n\nГрафічний бекенд було змінено: diff --git a/assets/lang/vi_VN.ini b/assets/lang/vi_VN.ini index 110feade3cc6..376fed8a930d 100644 --- a/assets/lang/vi_VN.ini +++ b/assets/lang/vi_VN.ini @@ -479,6 +479,7 @@ Error reading file = có lỗi khi đọc file. Failed initializing CPU/Memory = Failed initializing CPU or memory Failed to load executable: = Failed to load executable: File corrupt = File corrupt +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = Lỗi đọc đĩa: ISO bị hỏng. GenericAllStartupError = PPSSPP failed to start up with any graphics backend. Try upgrading your graphics and other drivers. GenericBackendSwitchCrash = PPSSPP crashed while starting.\n\nThis usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched: diff --git a/assets/lang/zh_CN.ini b/assets/lang/zh_CN.ini index c5cfa3608231..31a79045d54b 100644 --- a/assets/lang/zh_CN.ini +++ b/assets/lang/zh_CN.ini @@ -479,6 +479,7 @@ Error reading file = 读取文件时出错。 Failed initializing CPU/Memory = 初始化CPU或内存失败 Failed to load executable: = 无法加载可执行文件: File corrupt = 文件损坏 +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = 游戏光盘读取错误:ISO损坏。 GenericAllStartupError = PPSSPP无法使用任何图形后端启动,请尝试升级图形和其他驱动程序。 GenericBackendSwitchCrash = 启动时PPSSPP崩溃了。\n\n这通常意味着图形驱动程序问题,请尝试升级图形驱动程序。\n\n图形后端已切换: diff --git a/assets/lang/zh_TW.ini b/assets/lang/zh_TW.ini index 053aed8858ff..a36b4bf51227 100644 --- a/assets/lang/zh_TW.ini +++ b/assets/lang/zh_TW.ini @@ -479,6 +479,7 @@ Error reading file = 無法讀取檔案 Failed initializing CPU/Memory = 無法初始化 CPU 或記憶體 Failed to load executable: = 無法載入可執行檔: File corrupt = 檔案損毀 +File not found: %1 = File not found: %1 Game disc read error - ISO corrupt = 遊戲光碟讀取錯誤:ISO 損毀 GenericAllStartupError = PPSSPP 無法以任何圖形後端啟動,請嘗試更新您的圖形和其他驅動程式 GenericBackendSwitchCrash = PPSSPP 在啟動時當機\n\n這通常表示出現了圖形驅動程式問題,請嘗試升級圖形驅動程式\n\n圖形後端已切換: From 99c467a3f614316d9b8f9e9c790d70c828793e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 10 Dec 2024 19:41:57 +0100 Subject: [PATCH 10/58] Fix visual issue in new Ge state viewer --- UI/ImDebugger/ImGe.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index fe8024d0742b..781eea2cbd0f 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -616,14 +616,16 @@ void DrawGeStateWindow(ImConfig &cfg, GPUDebugInterface *gpuDebug) { ImGui::TextUnformatted(info.uiName); ImGui::TableNextColumn(); } - char temp[256]; + if (rows[i].cmd != GE_CMD_NOP) { + char temp[256]; - const u32 value = gstate.cmdmem[info.cmd] & 0xFFFFFF; - const u32 otherValue = gstate.cmdmem[info.otherCmd] & 0xFFFFFF; - const u32 otherValue2 = gstate.cmdmem[info.otherCmd2] & 0xFFFFFF; + const u32 value = gstate.cmdmem[info.cmd] & 0xFFFFFF; + const u32 otherValue = gstate.cmdmem[info.otherCmd] & 0xFFFFFF; + const u32 otherValue2 = gstate.cmdmem[info.otherCmd2] & 0xFFFFFF; - FormatStateRow(gpuDebug, temp, sizeof(temp), info.fmt, value, true, otherValue, otherValue2); - ImGui::TextUnformatted(temp); + FormatStateRow(gpuDebug, temp, sizeof(temp), info.fmt, value, true, otherValue, otherValue2); + ImGui::TextUnformatted(temp); + } if (!enabled) ImGui::PopStyleColor(); } From d9b92efd0e30947b6086607ea053bf42c5e8f1ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Dec 2024 10:18:19 +0100 Subject: [PATCH 11/58] Add a "clickable address" control, to be used in many places in the debugger --- UI/ImDebugger/ImDebugger.cpp | 51 ++++++++++++++++++++++++++++++++++-- UI/ImDebugger/ImDebugger.h | 25 +++++++++++++++--- UI/ImDebugger/ImGe.cpp | 42 ++++++++++++++++++++++------- UI/ImDebugger/ImGe.h | 17 ++++++++++-- 4 files changed, 117 insertions(+), 18 deletions(-) diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index fab9a455bb81..68b233fef84f 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -41,6 +41,37 @@ extern bool g_TakeScreenshot; +// TODO: Style it. +// Left click performs the preferred action, if any. Right click opens a menu for more. +void ImClickableAddress(uint32_t addr, ImControl &control, ImCmd cmd) { + char temp[32]; + snprintf(temp, sizeof(temp), "%08x", addr); + if (ImGui::SmallButton(temp)) { + control.command = { cmd, addr }; + } + + // Create a right-click popup menu + if (ImGui::BeginPopupContextItem(temp)) { + if (ImGui::MenuItem("Copy address to clipboard")) { + System_CopyStringToClipboard(temp); + } + ImGui::Separator(); + // Enable when we implement the memory viewer + if (cmd != ImCmd::SHOW_IN_MEMORY_VIEWER) { + if (ImGui::MenuItem("Show in memory viewer 1")) { + control.command = { ImCmd::SHOW_IN_MEMORY_VIEWER, addr }; + } + } + if (ImGui::MenuItem("Show in CPU debugger")) { + control.command = { ImCmd::SHOW_IN_CPU_DISASM, addr }; + } + if (ImGui::MenuItem("Show in GE debugger")) { + control.command = { ImCmd::SHOW_IN_GE_DISASM, addr }; + } + ImGui::EndPopup(); + } +} + void DrawSchedulerView(ImConfig &cfg) { ImGui::SetNextWindowSize(ImVec2(420, 300), ImGuiCond_FirstUseEver); if (!ImGui::Begin("Event Scheduler", &cfg.schedulerOpen)) { @@ -842,6 +873,8 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu return; } + ImControl control{}; + if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("Debug")) { switch (coreState) { @@ -1024,16 +1057,30 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu } if (cfg_.geDebuggerOpen) { - geDebugger_.Draw(cfg_, gpuDebug); + geDebugger_.Draw(cfg_, control, gpuDebug); } if (cfg_.geStateOpen) { - DrawGeStateWindow(cfg_, gpuDebug); + geStateWindow_.Draw(cfg_, control, gpuDebug); } if (cfg_.schedulerOpen) { DrawSchedulerView(cfg_); } + + // Process UI commands + switch (control.command.cmd) { + case ImCmd::SHOW_IN_CPU_DISASM: + disasm_.View().gotoAddr(control.command.param); + break; + case ImCmd::SHOW_IN_GE_DISASM: + geDebugger_.View().GotoAddr(control.command.param); + break; + } +} + +void ImDebugger::Snapshot() { + } void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, CoreState coreState) { diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h index 5ab01e9feddd..036b6162ac7f 100644 --- a/UI/ImDebugger/ImDebugger.h +++ b/UI/ImDebugger/ImDebugger.h @@ -103,12 +103,21 @@ struct ImConfig { void SyncConfig(IniFile *ini, bool save); }; -enum ImUiCmd { - TRIGGER_FIND_POPUP = 0, +enum class ImCmd { + NONE = 0, + TRIGGER_FIND_POPUP, + SHOW_IN_CPU_DISASM, + SHOW_IN_GE_DISASM, + SHOW_IN_MEMORY_VIEWER, }; -struct ImUiCommand { - ImUiCmd cmd; +struct ImCommand { + ImCmd cmd; + uint32_t param; +}; + +struct ImControl { + ImCommand command; }; class ImDebugger { @@ -118,6 +127,10 @@ class ImDebugger { void Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebug); + // Should be called just before starting a step or run, so that things can + // save state that they can later compare with, to highlight changes. + void Snapshot(); + private: Path ConfigPath(); @@ -125,8 +138,12 @@ class ImDebugger { ImDisasmWindow disasm_; ImGeDebuggerWindow geDebugger_; + ImGeStateWindow geStateWindow_; ImStructViewer structViewer_; // Open variables. ImConfig cfg_{}; }; + +// Simple custom controls +void ImClickableAddress(uint32_t addr, ImControl &control, ImCmd cmd); diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index 781eea2cbd0f..433b1a08b55d 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -236,7 +236,7 @@ void ImGeDisasmView::Draw(GPUDebugInterface *gpuDebug) { } } -void ImGeDebuggerWindow::Draw(ImConfig &cfg, GPUDebugInterface *gpuDebug) { +void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterface *gpuDebug) { ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); if (!ImGui::Begin("GE Debugger", &cfg.geDebuggerOpen)) { ImGui::End(); @@ -304,11 +304,18 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, GPUDebugInterface *gpuDebug) { // Display any pending step event. if (GPUDebug::GetBreakNext() != GPUDebug::BreakNext::NONE) { - ImGui::Text("Step pending (waiting for CPU): %s", GPUDebug::BreakNextToString(GPUDebug::GetBreakNext())); - ImGui::SameLine(); - if (ImGui::Button("Cancel step")) { - GPUDebug::SetBreakNext(GPUDebug::BreakNext::NONE); + if (showBannerInFrames_ > 0) { + showBannerInFrames_--; } + if (showBannerInFrames_ == 0) { + ImGui::Text("Step pending (waiting for CPU): %s", GPUDebug::BreakNextToString(GPUDebug::GetBreakNext())); + ImGui::SameLine(); + if (ImGui::Button("Cancel step")) { + GPUDebug::SetBreakNext(GPUDebug::BreakNext::NONE); + } + } + } else { + showBannerInFrames_ = 2; } // First, let's list any active display lists in the left column, on top of the disassembly. @@ -320,8 +327,11 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, GPUDebugInterface *gpuDebug) { char title[64]; snprintf(title, sizeof(title), "List %d", list.id); if (ImGui::CollapsingHeader(title, ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Text("PC: %08x", list.pc); - ImGui::Text("StartPC: %08x", list.startpc); + ImGui::TextUnformatted("PC:"); + ImGui::SameLine(); + ImClickableAddress(list.pc, control, ImCmd::SHOW_IN_GE_DISASM); + ImGui::Text("StartPC:"); + ImClickableAddress(list.startpc, control, ImCmd::SHOW_IN_GE_DISASM); ImGui::Text("Pending interrupt: %d", (int)list.pendingInterrupt); ImGui::Text("Stack depth: %d", (int)list.stackptr); ImGui::Text("BBOX result: %d", (int)list.bboxResult); @@ -571,8 +581,12 @@ static const StateItem g_vertexState[] = { {false, GE_CMD_PATCHFACING}, }; +void ImGeStateWindow::Snapshot() { + +} + // TODO: Separate window or merge into Ge debugger? -void DrawGeStateWindow(ImConfig &cfg, GPUDebugInterface *gpuDebug) { +void ImGeStateWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterface *gpuDebug) { ImGui::SetNextWindowSize(ImVec2(300, 500), ImGuiCond_FirstUseEver); if (!ImGui::Begin("GE State", &cfg.geStateOpen)) { ImGui::End(); @@ -623,8 +637,16 @@ void DrawGeStateWindow(ImConfig &cfg, GPUDebugInterface *gpuDebug) { const u32 otherValue = gstate.cmdmem[info.otherCmd] & 0xFFFFFF; const u32 otherValue2 = gstate.cmdmem[info.otherCmd2] & 0xFFFFFF; - FormatStateRow(gpuDebug, temp, sizeof(temp), info.fmt, value, true, otherValue, otherValue2); - ImGui::TextUnformatted(temp); + // Special handling for pointer and pointer/width entries + if (info.fmt == CMD_FMT_PTRWIDTH) { + const u32 val = value | (otherValue & 0x00FF0000) << 8; + ImClickableAddress(val, control, ImCmd::NONE); + ImGui::SameLine(); + ImGui::Text("w=%d", otherValue & 0xFFFF); + } else { + FormatStateRow(gpuDebug, temp, sizeof(temp), info.fmt, value, true, otherValue, otherValue2); + ImGui::TextUnformatted(temp); + } } if (!enabled) ImGui::PopStyleColor(); diff --git a/UI/ImDebugger/ImGe.h b/UI/ImDebugger/ImGe.h index e686d71e2f3f..a001977cf424 100644 --- a/UI/ImDebugger/ImGe.h +++ b/UI/ImDebugger/ImGe.h @@ -3,6 +3,7 @@ // GE-related windows of the ImDebugger struct ImConfig; +struct ImControl; class FramebufferManagerCommon; class TextureCacheCommon; @@ -12,7 +13,6 @@ void DrawFramebuffersWindow(ImConfig &cfg, FramebufferManagerCommon *framebuffer void DrawTexturesWindow(ImConfig &cfg, TextureCacheCommon *textureCache); void DrawDisplayWindow(ImConfig &cfg, FramebufferManagerCommon *framebufferManager); void DrawDebugStatsWindow(ImConfig &cfg); -void DrawGeStateWindow(ImConfig &cfg, GPUDebugInterface *gpuDebug); class ImGeDisasmView { public: @@ -24,6 +24,10 @@ class ImGeDisasmView { gotoPC_ = true; } + void GotoAddr(uint32_t addr) { + selectedAddr_ = addr; + } + private: u32 selectedAddr_ = INVALID_ADDR; u32 dragAddr_ = INVALID_ADDR; @@ -34,13 +38,22 @@ class ImGeDisasmView { }; }; +class ImGeStateWindow { +public: + void Draw(ImConfig &cfg, ImControl &control, GPUDebugInterface *gpuDebug); + void Snapshot(); +private: + u32 prevState_[256]{}; +}; + class ImGeDebuggerWindow { public: - void Draw(ImConfig &cfg, GPUDebugInterface *gpuDebug); + void Draw(ImConfig &cfg, ImControl &control, GPUDebugInterface *gpuDebug); ImGeDisasmView &View() { return disasmView_; } private: ImGeDisasmView disasmView_; + int showBannerInFrames_ = 0; }; From 97cc0ec1b11b4076ba62e9b7beb0b52d9aa80db9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Dec 2024 10:26:56 +0100 Subject: [PATCH 12/58] Use the new ImClickableAddress in a few more places --- UI/ImDebugger/ImDebugger.cpp | 34 ++++++++++++++++++++-------------- UI/ImDebugger/ImGe.cpp | 1 + 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 68b233fef84f..520da9c5027c 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -100,9 +100,9 @@ void DrawSchedulerView(ImConfig &cfg) { ImGui::End(); } -void DrawRegisterView(MIPSDebugInterface *mipsDebug, bool *open) { +void DrawRegisterView(ImConfig &config, ImControl &control, MIPSDebugInterface *mipsDebug) { ImGui::SetNextWindowSize(ImVec2(320, 600), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("Registers", open)) { + if (!ImGui::Begin("Registers", &config.regsOpen)) { ImGui::End(); return; } @@ -114,24 +114,30 @@ void DrawRegisterView(MIPSDebugInterface *mipsDebug, bool *open) { ImGui::TableSetupColumn("value", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("value_i", ImGuiTableColumnFlags_WidthStretch); - auto gprLine = [&](const char *regname, int value) { + auto gprLine = [&](int index, const char *regname, int value) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextUnformatted(regname); ImGui::TableNextColumn(); - ImGui::Text("%08x", value); + if (Memory::IsValid4AlignedAddress(value)) { + ImGui::PushID(index); + ImClickableAddress(value, control, index == MIPS_REG_RA ? ImCmd::SHOW_IN_CPU_DISASM : ImCmd::SHOW_IN_MEMORY_VIEWER); + ImGui::PopID(); + } else { + ImGui::Text("%08x", value); + } if (value >= -1000000 && value <= 1000000) { ImGui::TableSetColumnIndex(2); ImGui::Text("%d", value); } }; for (int i = 0; i < 32; i++) { - gprLine(mipsDebug->GetRegName(0, i).c_str(), mipsDebug->GetGPR32Value(i)); + gprLine(i, mipsDebug->GetRegName(0, i).c_str(), mipsDebug->GetGPR32Value(i)); } - gprLine("hi", mipsDebug->GetHi()); - gprLine("lo", mipsDebug->GetLo()); - gprLine("pc", mipsDebug->GetPC()); - gprLine("ll", mipsDebug->GetLLBit()); + gprLine(32, "hi", mipsDebug->GetHi()); + gprLine(33, "lo", mipsDebug->GetLo()); + gprLine(34, "pc", mipsDebug->GetPC()); + gprLine(35, "ll", mipsDebug->GetLLBit()); ImGui::EndTable(); } @@ -238,7 +244,7 @@ void WaitIDToString(WaitType waitType, SceUID waitID, char *buffer, size_t bufSi } -void DrawThreadView(ImConfig &cfg) { +void DrawThreadView(ImConfig &cfg, ImControl &control) { ImGui::SetNextWindowSize(ImVec2(420, 300), ImGuiCond_FirstUseEver); if (!ImGui::Begin("Threads", &cfg.threadsOpen)) { ImGui::End(); @@ -273,9 +279,9 @@ void DrawThreadView(ImConfig &cfg) { ImGui::OpenPopup("threadPopup"); } ImGui::TableNextColumn(); - ImGui::Text("%08x", thread.curPC); + ImClickableAddress(thread.curPC, control, ImCmd::SHOW_IN_CPU_DISASM); ImGui::TableNextColumn(); - ImGui::Text("%08x", thread.entrypoint); + ImClickableAddress(thread.entrypoint, control, ImCmd::SHOW_IN_CPU_DISASM); ImGui::TableNextColumn(); ImGui::Text("%d", thread.priority); ImGui::TableNextColumn(); @@ -997,7 +1003,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu } if (cfg_.regsOpen) { - DrawRegisterView(mipsDebug, &cfg_.regsOpen); + DrawRegisterView(cfg_, control, mipsDebug); } if (cfg_.breakpointsOpen) { @@ -1017,7 +1023,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu } if (cfg_.threadsOpen) { - DrawThreadView(cfg_); + DrawThreadView(cfg_, control); } if (cfg_.callstackOpen) { diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index 433b1a08b55d..a6a450b58848 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -331,6 +331,7 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa ImGui::SameLine(); ImClickableAddress(list.pc, control, ImCmd::SHOW_IN_GE_DISASM); ImGui::Text("StartPC:"); + ImGui::SameLine(); ImClickableAddress(list.startpc, control, ImCmd::SHOW_IN_GE_DISASM); ImGui::Text("Pending interrupt: %d", (int)list.pendingInterrupt); ImGui::Text("Stack depth: %d", (int)list.stackptr); From cdef529aa330424df114ce9132f298d6f18d1256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 28 Nov 2024 09:33:48 +0100 Subject: [PATCH 13/58] Initial memory view work, not yet building --- CMakeLists.txt | 2 + UI/ImDebugger/ImMemView.cpp | 889 ++++++++++++++++++++++++++++++ UI/ImDebugger/ImMemView.h | 116 ++++ UI/UI.vcxproj | 2 + UI/UI.vcxproj.filters | 6 + UWP/UI_UWP/UI_UWP.vcxproj | 2 + UWP/UI_UWP/UI_UWP.vcxproj.filters | 6 + Windows/Debugger/CtrlMemView.h | 10 +- android/jni/Android.mk | 1 + 9 files changed, 1026 insertions(+), 8 deletions(-) create mode 100644 UI/ImDebugger/ImMemView.cpp create mode 100644 UI/ImDebugger/ImMemView.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 89e6347f1b4e..61473590efc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1528,6 +1528,8 @@ list(APPEND NativeAppSource UI/ImDebugger/ImGe.h UI/ImDebugger/ImDisasmView.cpp UI/ImDebugger/ImDisasmView.h + UI/ImDebugger/ImMemView.cpp + UI/ImDebugger/ImMemView.h UI/ImDebugger/ImStructViewer.cpp UI/ImDebugger/ImStructViewer.h UI/DiscordIntegration.cpp diff --git a/UI/ImDebugger/ImMemView.cpp b/UI/ImDebugger/ImMemView.cpp new file mode 100644 index 000000000000..406fd0390a73 --- /dev/null +++ b/UI/ImDebugger/ImMemView.cpp @@ -0,0 +1,889 @@ +#include +#include +#include +#include + +#include "ext/imgui/imgui.h" +#include "ext/imgui/imgui_impl_thin3d.h" + +#include "ext/xxhash.h" +#include "Common/StringUtils.h" +#include "Core/Config.h" +#include "Core/MemMap.h" +#include "Core/Reporting.h" +#include "Core/RetroAchievements.h" +#include "Common/System/Display.h" + +#include "UI/ImDebugger/ImDebugger.h" +#include "UI/ImDebugger/ImMemView.h" +// #include "DumpMemoryWindow.h" + +ImMemView::ImMemView() { + const float fontScale = 1.0f / g_display.dpi_scale_real_y; + charWidth_ = g_Config.iFontWidth * fontScale; + rowHeight_ = g_Config.iFontHeight * fontScale; + offsetPositionY_ = offsetLine * rowHeight_; + + windowStart_ = curAddress_; + selectRangeStart_ = curAddress_; + selectRangeEnd_ = curAddress_ + 1; + lastSelectReset_ = curAddress_; + + addressStartX_ = charWidth_; + hexStartX_ = addressStartX_ + 9 * charWidth_; + asciiStartX_ = hexStartX_ + (rowSize_ * 3 + 1) * charWidth_; +} + +ImMemView::~ImMemView() {} + +/* +LRESULT CALLBACK ImMemView::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + ImMemView *ccp = ImMemView::getFrom(hwnd); + static bool lmbDown = false, rmbDown = false; + + switch (msg) { + case WM_NCCREATE: + // Allocate a new CustCtrl structure for this window. + ccp = new ImMemView(hwnd); + + // Continue with window creation. + return ccp != NULL; + + // Clean up when the window is destroyed. + case WM_NCDESTROY: + delete ccp; + break; + case WM_SETFONT: + break; + case WM_SIZE: + ccp->redraw(); + break; + case WM_PAINT: + ccp->onPaint(wParam, lParam); + break; + case WM_VSCROLL: + ccp->onVScroll(wParam, lParam); + break; + case WM_MOUSEWHEEL: + if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) { + ccp->ScrollWindow(-3, GotoModeFromModifiers(false)); + } else if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) { + ccp->ScrollWindow(3, GotoModeFromModifiers(false)); + } + break; + case WM_ERASEBKGND: + return FALSE; + case WM_KEYDOWN: + ccp->onKeyDown(wParam, lParam); + return 0; + case WM_CHAR: + ccp->onChar(wParam, lParam); + return 0; + case WM_KEYUP: + return 0; + case WM_LBUTTONDOWN: SetFocus(hwnd); lmbDown = true; ccp->onMouseDown(wParam, lParam, 1); break; + case WM_RBUTTONDOWN: SetFocus(hwnd); rmbDown = true; ccp->onMouseDown(wParam, lParam, 2); break; + case WM_MOUSEMOVE: ccp->onMouseMove(wParam, lParam, (lmbDown ? 1 : 0) | (rmbDown ? 2 : 0)); break; + case WM_LBUTTONUP: lmbDown = false; ccp->onMouseUp(wParam, lParam, 1); break; + case WM_RBUTTONUP: rmbDown = false; ccp->onMouseUp(wParam, lParam, 2); break; + case WM_SETFOCUS: + SetFocus(hwnd); + ccp->hasFocus_ = true; + ccp->redraw(); + break; + case WM_KILLFOCUS: + ccp->hasFocus_ = false; + ccp->redraw(); + break; + case WM_GETDLGCODE: // we want to process the arrow keys and all characters ourselves + return DLGC_WANTARROWS | DLGC_WANTCHARS | DLGC_WANTTAB; + break; + case WM_TIMER: + // This is actually delayed too, using another timer. That way we won't update twice. + if (wParam == IDT_REDRAW_AUTO && IsWindowVisible(ccp->wnd)) + ccp->redraw(); + + if (wParam == IDT_REDRAW_DELAYED) { + InvalidateRect(hwnd, nullptr, FALSE); + UpdateWindow(hwnd); + ccp->redrawScheduled_ = false; + KillTimer(hwnd, wParam); + } + break; + default: + break; + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} +*/ + +void ImMemView::Draw(ImDrawList *drawList) { + auto memLock = Memory::Lock(); + + visibleRows_ = rect_.bottom / rowHeight_; + + if (displayOffsetScale_) { + // visibleRows_ is calculated based on the size of the control, but X rows have already been used for the offsets and are no longer usable + visibleRows_ -= offsetSpace; + } + + ImGui_PushFixedFont(); + + // Background fill + Rectangle(hdc, 0, 0, rect_.right, rect_.bottom); + + if (displayOffsetScale_) + drawOffsetScale(); + + std::vector memRangeInfo = FindMemInfoByFlag(highlightFlags_, windowStart_, (visibleRows_ + 1) * rowSize_); + + _assert_msg_(((windowStart_ | rowSize_) & 3) == 0, "readMemory() can't handle unaligned reads"); + + // draw one extra row that may be partially visible + for (int i = 0; i < visibleRows_ + 1; i++) { + int rowY = rowHeight_ * i; + // Skip the first X rows to make space for the offsets. + if (displayOffsetScale_) + rowY += rowHeight_ * offsetSpace; + + char temp[32]; + uint32_t address = windowStart_ + i * rowSize_; + snprintf(temp, sizeof(temp), "%08X", address); + + setTextColors(0x600000, standardBG); + TextOutA(hdc, addressStartX_, rowY, temp, (int)strlen(temp)); + + union { + uint32_t words[4]; + uint8_t bytes[16]; + } memory; + int valid = debugger_ != nullptr && debugger_->isAlive() ? Memory::ValidSize(address, 16) / 4 : 0; + for (int i = 0; i < valid; ++i) { + memory.words[i] = debugger_->readMemory(address + i * 4); + } + + for (int j = 0; j < rowSize_; j++) { + const uint32_t byteAddress = (address + j) & ~0xC0000000; + std::string tag; + bool tagContinues = false; + for (auto info : memRangeInfo) { + if (info.start <= byteAddress && info.start + info.size > byteAddress) { + tag = info.tag; + tagContinues = byteAddress + 1 < info.start + info.size; + } + } + + int hexX = hexStartX_ + j * 3 * charWidth_; + int hexLen = 2; + int asciiX = asciiStartX_ + j * (charWidth_ + 2); + + char c; + if (valid) { + snprintf(temp, sizeof(temp), "%02X ", memory.bytes[j]); + c = (char)memory.bytes[j]; + if (memory.bytes[j] < 32 || memory.bytes[j] >= 128) + c = '.'; + } else { + truncate_cpy(temp, "??"); + c = '.'; + } + + COLORREF hexBGCol = standardBG; + COLORREF hexTextCol = 0x000000; + COLORREF continueBGCol = standardBG; + COLORREF asciiBGCol = standardBG; + COLORREF asciiTextCol = 0x000000; + int underline = -1; + + if (address + j >= selectRangeStart_ && address + j < selectRangeEnd_ && !searching_) { + if (asciiSelected_) { + hexBGCol = 0xC0C0C0; + hexTextCol = 0x000000; + asciiBGCol = hasFocus_ ? 0xFF9933 : 0xC0C0C0; + asciiTextCol = hasFocus_ ? 0xFFFFFF : 0x000000; + } else { + hexBGCol = hasFocus_ ? 0xFF9933 : 0xC0C0C0; + hexTextCol = hasFocus_ ? 0xFFFFFF : 0x000000; + asciiBGCol = 0xC0C0C0; + asciiTextCol = 0x000000; + if (address + j == curAddress_) + underline = selectedNibble_; + } + if (!tag.empty() && tagContinues) { + continueBGCol = pickTagColor(tag); + } + } else if (!tag.empty()) { + hexBGCol = pickTagColor(tag); + continueBGCol = hexBGCol; + asciiBGCol = pickTagColor(tag); + hexLen = tagContinues ? 3 : 2; + } + + setTextColors(hexTextCol, hexBGCol); + if (underline >= 0) { + SelectObject(hdc, underline == 0 ? (HGDIOBJ)underlineFont : (HGDIOBJ)font); + TextOutA(hdc, hexX, rowY, &temp[0], 1); + SelectObject(hdc, underline == 0 ? (HGDIOBJ)font : (HGDIOBJ)underlineFont); + TextOutA(hdc, hexX + charWidth_, rowY, &temp[1], 1); + SelectObject(hdc, (HGDIOBJ)font); + + // If the tag keeps going, draw the BG too. + if (continueBGCol != standardBG) { + setTextColors(0x000000, continueBGCol); + TextOutA(hdc, hexX + charWidth_ * 2, rowY, &temp[2], 1); + } + } else { + if (continueBGCol != hexBGCol) { + TextOutA(hdc, hexX, rowY, temp, 2); + setTextColors(0x000000, continueBGCol); + TextOutA(hdc, hexX + charWidth_ * 2, rowY, &temp[2], 1); + } else { + TextOutA(hdc, hexX, rowY, temp, hexLen); + } + } + + setTextColors(asciiTextCol, asciiBGCol); + TextOutA(hdc, asciiX, rowY, &c, 1); + } + } + + ImGui_PopFont(); +} + +// We don't have a scroll bar - though maybe we should build one. +/* +void ImMemView::onVScroll(WPARAM wParam, LPARAM lParam) { + switch (wParam & 0xFFFF) { + case SB_LINEDOWN: + ScrollWindow(1, GotoModeFromModifiers(false)); + break; + case SB_LINEUP: + ScrollWindow(-1, GotoModeFromModifiers(false)); + break; + case SB_PAGEDOWN: + ScrollWindow(visibleRows_, GotoModeFromModifiers(false)); + break; + case SB_PAGEUP: + ScrollWindow(-visibleRows_, GotoModeFromModifiers(false)); + break; + default: + return; + } +} +*/ + +void ImMemView::ProcessKeyboardShortcuts(bool focused) { + if (!focused) { + return; + } + + ImGuiIO& io = ImGui::GetIO(); + + if (io.KeyMods & ImGuiMod_Ctrl) { + if (ImGui::IsKeyPressed(ImGuiKey_G)) { + u32 addr; + if (executeExpressionWindow(wnd, debugger_, addr) == false) + return; + gotoAddr(addr); + return; + } + if (ImGui::IsKeyPressed(ImGuiKey_F)) { + search(false); + } + if (ImGui::IsKeyPressed(ImGuiKey_C)) { + search(true); + } + } else { + if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) { + ScrollCursor(rowSize_, GotoModeFromModifiers(false)); + } + if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) { + ScrollCursor(-rowSize_, GotoModeFromModifiers(false)); + } + if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) { + ScrollCursor(-1, GotoModeFromModifiers(false)); + } + if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) { + ScrollCursor(1, GotoModeFromModifiers(false)); + } + if (ImGui::IsKeyPressed(ImGuiKey_PageDown)) { + ScrollWindow(visibleRows_, GotoModeFromModifiers(false)); + } + if (ImGui::IsKeyPressed(ImGuiKey_PageUp)) { + ScrollWindow(-visibleRows_, GotoModeFromModifiers(false)); + } + } +} + +void ImMemView::onChar(int c) { + auto memLock = Memory::Lock(); + if (!PSP_IsInited()) + return; + + ImGuiIO& io = ImGui::GetIO(); + + if (io.KeyMods & ImGuiMod_Ctrl) { + return; + + if (!Memory::IsValidAddress(curAddress_)) { + ScrollCursor(1, GotoMode::RESET); + return; + } + + bool active = Core_IsActive(); + if (active) + Core_Break("memory.access", curAddress_); + + if (asciiSelected_) { + Memory::WriteUnchecked_U8((u8)c, curAddress_); + ScrollCursor(1, GotoMode::RESET); + } else { + c = tolower(c); + int inputValue = -1; + + if (c >= '0' && c <= '9') inputValue = c - '0'; + if (c >= 'a' && c <= 'f') inputValue = c - 'a' + 10; + + if (inputValue >= 0) { + int shiftAmount = (1 - selectedNibble_) * 4; + + u8 oldValue = Memory::ReadUnchecked_U8(curAddress_); + oldValue &= ~(0xF << shiftAmount); + u8 newValue = oldValue | (inputValue << shiftAmount); + Memory::WriteUnchecked_U8(newValue, curAddress_); + ScrollCursor(1, GotoMode::RESET); + } + } + + Reporting::NotifyDebugger(); + if (active) + Core_Resume(); +} + +ImMemView::GotoMode ImMemView::GotoModeFromModifiers(bool isRightClick) { + GotoMode mode = GotoMode::RESET; + if (isRightClick) { + mode = GotoMode::RESET_IF_OUTSIDE; + } else if (KeyDownAsync(VK_SHIFT)) { + if (KeyDownAsync(VK_CONTROL)) + mode = GotoMode::EXTEND; + else + mode = GotoMode::FROM_CUR; + } + return mode; +} + +void ImMemView::onMouseDown(WPARAM wParam, LPARAM lParam, int button) { + if (Achievements::HardcoreModeActive()) + return; + + int x = LOWORD(lParam); + int y = HIWORD(lParam); + + GotoPoint(x, y, GotoModeFromModifiers(button == 2)); +} + +void ImMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) { + if (Achievements::HardcoreModeActive()) + return; + + if (button == 2) { + int32_t selectedSize = selectRangeEnd_ - selectRangeStart_; + bool enable16 = !asciiSelected_ && (selectedSize == 1 || (selectedSize & 1) == 0); + bool enable32 = !asciiSelected_ && (selectedSize == 1 || (selectedSize & 3) == 0); + + HMENU menu = GetContextMenu(ContextMenuID::MEMVIEW); + EnableMenuItem(menu, ID_MEMVIEW_COPYVALUE_16, enable16 ? MF_ENABLED : MF_GRAYED); + EnableMenuItem(menu, ID_MEMVIEW_COPYVALUE_32, enable32 ? MF_ENABLED : MF_GRAYED); + EnableMenuItem(menu, ID_MEMVIEW_COPYFLOAT_32, enable32 ? MF_ENABLED : MF_GRAYED); + + switch (TriggerContextMenu(ContextMenuID::MEMVIEW, wnd, ContextPoint::FromEvent(lParam))) { + case ID_MEMVIEW_DUMP: + { + DumpMemoryWindow dump(wnd, debugger_); + dump.exec(); + break; + } + + case ID_MEMVIEW_COPYVALUE_8: + { + auto memLock = Memory::Lock(); + size_t tempSize = 3 * selectedSize + 1; + char *temp = new char[tempSize]; + memset(temp, 0, tempSize); + + // it's admittedly not really useful like this + if (asciiSelected_) { + for (uint32_t p = selectRangeStart_; p != selectRangeEnd_; ++p) { + uint8_t c = Memory::IsValidAddress(p) ? Memory::ReadUnchecked_U8(p) : '.'; + if (c < 32 || c >= 128) + c = '.'; + temp[p - selectRangeStart_] = c; + } + } else { + char *pos = temp; + for (uint32_t p = selectRangeStart_; p != selectRangeEnd_; ++p) { + uint8_t c = Memory::IsValidAddress(p) ? Memory::ReadUnchecked_U8(p) : 0xFF; + pos += snprintf(pos, tempSize - (pos - temp + 1), "%02X ", c); + } + // Clear the last space. + if (pos > temp) + *(pos - 1) = '\0'; + } + W32Util::CopyTextToClipboard(wnd, temp); + delete[] temp; + } + break; + + case ID_MEMVIEW_COPYVALUE_16: + { + auto memLock = Memory::Lock(); + size_t tempSize = 5 * ((selectedSize + 1) / 2) + 1; + char *temp = new char[tempSize]; + memset(temp, 0, tempSize); + + char *pos = temp; + for (uint32_t p = selectRangeStart_; p < selectRangeEnd_; p += 2) { + uint16_t c = Memory::IsValidRange(p, 2) ? Memory::ReadUnchecked_U16(p) : 0xFFFF; + pos += snprintf(pos, tempSize - (pos - temp + 1), "%04X ", c); + } + // Clear the last space. + if (pos > temp) + *(pos - 1) = '\0'; + + W32Util::CopyTextToClipboard(wnd, temp); + delete[] temp; + } + break; + + case ID_MEMVIEW_COPYVALUE_32: + { + auto memLock = Memory::Lock(); + size_t tempSize = 9 * ((selectedSize + 3) / 4) + 1; + char *temp = new char[tempSize]; + memset(temp, 0, tempSize); + + char *pos = temp; + for (uint32_t p = selectRangeStart_; p < selectRangeEnd_; p += 4) { + uint32_t c = Memory::IsValidRange(p, 4) ? Memory::ReadUnchecked_U32(p) : 0xFFFFFFFF; + pos += snprintf(pos, tempSize - (pos - temp + 1), "%08X ", c); + } + // Clear the last space. + if (pos > temp) + *(pos - 1) = '\0'; + + W32Util::CopyTextToClipboard(wnd, temp); + delete[] temp; + } + break; + + case ID_MEMVIEW_COPYFLOAT_32: + { + auto memLock = Memory::Lock(); + std::ostringstream stream; + stream << (Memory::IsValidAddress(curAddress_) ? Memory::Read_Float(curAddress_) : NAN); + auto temp_string = stream.str(); + W32Util::CopyTextToClipboard(wnd, temp_string.c_str()); + } + break; + + case ID_MEMVIEW_EXTENTBEGIN: + { + std::vector memRangeInfo = FindMemInfoByFlag(highlightFlags_, curAddress_, 1); + uint32_t addr = curAddress_; + for (MemBlockInfo info : memRangeInfo) { + addr = info.start; + } + gotoAddr(addr); + break; + } + + case ID_MEMVIEW_EXTENTEND: + { + std::vector memRangeInfo = FindMemInfoByFlag(highlightFlags_, curAddress_, 1); + uint32_t addr = curAddress_; + for (MemBlockInfo info : memRangeInfo) { + addr = info.start + info.size - 1; + } + gotoAddr(addr); + break; + } + + case ID_MEMVIEW_COPYADDRESS: + { + char temp[24]; + snprintf(temp, sizeof(temp), "0x%08X", curAddress_); + W32Util::CopyTextToClipboard(wnd, temp); + } + break; + + case ID_MEMVIEW_GOTOINDISASM: + if (disasmWindow) { + disasmWindow->Goto(curAddress_); + disasmWindow->Show(true); + } + break; + } + return; + } + + int x = LOWORD(lParam); + int y = HIWORD(lParam); + ReleaseCapture(); + GotoPoint(x, y, GotoModeFromModifiers(button == 2)); +} + +void ImMemView::onMouseMove(WPARAM wParam, LPARAM lParam, int button) { + if (Achievements::HardcoreModeActive()) + return; + + int x = LOWORD(lParam); + int y = HIWORD(lParam); + + if (button & 1) { + GotoPoint(x, y, GotoModeFromModifiers(button == 2)); + } +} + +void ImMemView::updateStatusBarText() { + std::vector memRangeInfo = FindMemInfoByFlag(highlightFlags_, curAddress_, 1); + + char text[512]; + snprintf(text, sizeof(text), "%08X", curAddress_); + // There should only be one. + for (MemBlockInfo info : memRangeInfo) { + snprintf(text, sizeof(text), "%08X - %s %08X-%08X (at PC %08X / %lld ticks)", curAddress_, info.tag.c_str(), info.start, info.start + info.size, info.pc, info.ticks); + } + + SendMessage(GetParent(wnd), WM_DEB_SETSTATUSBARTEXT, 0, (LPARAM)text); +} + +void ImMemView::UpdateSelectRange(uint32_t target, GotoMode mode) { + if (mode == GotoMode::FROM_CUR && lastSelectReset_ == 0) { + lastSelectReset_ = curAddress_; + } + + switch (mode) { + case GotoMode::RESET: + selectRangeStart_ = target; + selectRangeEnd_ = target + 1; + lastSelectReset_ = target; + break; + + case GotoMode::RESET_IF_OUTSIDE: + if (target < selectRangeStart_ || target >= selectRangeEnd_) { + selectRangeStart_ = target; + selectRangeEnd_ = target + 1; + lastSelectReset_ = target; + } + break; + + case GotoMode::FROM_CUR: + selectRangeStart_ = lastSelectReset_ > target ? target : lastSelectReset_; + selectRangeEnd_ = selectRangeStart_ == lastSelectReset_ ? target + 1 : lastSelectReset_ + 1; + break; + + case GotoMode::EXTEND: + if (target < selectRangeStart_) + selectRangeStart_ = target; + if (target > selectRangeEnd_) + selectRangeEnd_ = target; + break; + } + curAddress_ = target; +} + +void ImMemView::GotoPoint(int x, int y, GotoMode mode) { + int line = y / rowHeight_; + int lineAddress = windowStart_ + line * rowSize_; + + if (displayOffsetScale_) { + // ignore clicks on the offset space + if (line < offsetSpace) { + updateStatusBarText(); + redraw(); + return; + } + // since each row has been written X rows down from where the window expected it to be written the target of the clicks must be adjusted + lineAddress -= rowSize_ * offsetSpace; + } + + uint32_t target = curAddress_; + uint32_t targetNibble = selectedNibble_; + bool targetAscii = asciiSelected_; + if (x >= asciiStartX_) { + int col = (x - asciiStartX_) / (charWidth_ + 2); + if (col >= rowSize_) + return; + + targetAscii = true; + target = lineAddress + col; + targetNibble = 0; + } else if (x >= hexStartX_) { + int col = (x - hexStartX_) / charWidth_; + if ((col / 3) >= rowSize_) + return; + + switch (col % 3) { + case 0: targetNibble = 0; break; + case 1: targetNibble = 1; break; + case 2: return; // don't change position when clicking on the space + } + + targetAscii = false; + target = lineAddress + col / 3; + } + + if (target != curAddress_ || targetNibble != selectedNibble_ || targetAscii != asciiSelected_) { + selectedNibble_ = targetNibble; + asciiSelected_ = targetAscii; + UpdateSelectRange(target, mode); + + updateStatusBarText(); + redraw(); + } +} + +void ImMemView::gotoAddr(unsigned int addr) { + int lines = rect_.bottom / rowHeight_; + u32 windowEnd = windowStart_ + lines * rowSize_; + + curAddress_ = addr; + lastSelectReset_ = curAddress_; + selectRangeStart_ = curAddress_; + selectRangeEnd_ = curAddress_ + 1; + selectedNibble_ = 0; + + if (curAddress_ < windowStart_ || curAddress_ >= windowEnd) { + windowStart_ = curAddress_ & ~15; + } + + updateStatusBarText(); + redraw(); +} + +void ImMemView::ScrollWindow(int lines, GotoMode mode) { + windowStart_ += lines * rowSize_; + + UpdateSelectRange(curAddress_ + lines * rowSize_, mode); + + updateStatusBarText(); + redraw(); +} + +void ImMemView::ScrollCursor(int bytes, GotoMode mode) { + if (!asciiSelected_ && bytes == 1) { + if (selectedNibble_ == 0) { + selectedNibble_ = 1; + bytes = 0; + } else { + selectedNibble_ = 0; + } + } else if (!asciiSelected_ && bytes == -1) { + if (selectedNibble_ == 0) { + selectedNibble_ = 1; + } else { + selectedNibble_ = 0; + bytes = 0; + } + } + + UpdateSelectRange(curAddress_ + bytes, mode); + + u32 windowEnd = windowStart_ + visibleRows_ * rowSize_; + if (curAddress_ < windowStart_) { + windowStart_ = curAddress_ & ~15; + } else if (curAddress_ >= windowEnd) { + windowStart_ = (curAddress_ - (visibleRows_ - 1) * rowSize_) & ~15; + } + + updateStatusBarText(); + redraw(); +} + +bool ImMemView::ParseSearchString(const std::string &query, bool asHex, std::vector &data) { + data.clear(); + if (!asHex) { + for (size_t i = 0; i < query.length(); i++) { + data.push_back(query[i]); + } + return true; + } + + for (size_t index = 0; index < query.size(); ) { + if (isspace(query[index])) { + index++; + continue; + } + + u8 value = 0; + for (int i = 0; i < 2 && index < query.size(); i++) { + char c = tolower(query[index++]); + if (c >= 'a' && c <= 'f') { + value |= (c - 'a' + 10) << (1 - i) * 4; + } else if (c >= '0' && c <= '9') { + value |= (c - '0') << (1 - i) * 4; + } else { + return false; + } + } + + data.push_back(value); + } + + return true; +} + +std::vector ImMemView::searchString(const std::string &searchQuery) { + std::vector searchResAddrs; + + auto memLock = Memory::Lock(); + if (!PSP_IsInited()) + return searchResAddrs; + + std::vector searchData; + if (!ParseSearchString(searchQuery, false, searchData)) + return searchResAddrs; + + if (searchData.empty()) + return searchResAddrs; + + std::vector> memoryAreas; + memoryAreas.emplace_back(PSP_GetScratchpadMemoryBase(), PSP_GetScratchpadMemoryEnd()); + // Ignore the video memory mirrors. + memoryAreas.emplace_back(PSP_GetVidMemBase(), 0x04200000); + memoryAreas.emplace_back(PSP_GetKernelMemoryBase(), PSP_GetUserMemoryEnd()); + + for (const auto &area : memoryAreas) { + const u32 segmentStart = area.first; + const u32 segmentEnd = area.second - (u32)searchData.size(); + + for (u32 pos = segmentStart; pos < segmentEnd; pos++) { + if ((pos % 256) == 0 && ImGui::IsKeyDown(ImGuiKey_Escape)) { + return searchResAddrs; + } + + const u8 *ptr = Memory::GetPointerUnchecked(pos); + if (memcmp(ptr, searchData.data(), searchData.size()) == 0) { + searchResAddrs.push_back(pos); + } + } + } + + return searchResAddrs; +}; + +void ImMemView::search(bool continueSearch) { + auto memLock = Memory::Lock(); + if (!PSP_IsInited()) + return; + + u32 searchAddress = 0; + u32 segmentStart = 0; + u32 segmentEnd = 0; + if (continueSearch == false || searchQuery_.empty()) { + if (InputBox_GetString(GetModuleHandle(NULL), wnd, L"Search for", searchQuery_, searchQuery_) == false) { + SetFocus(wnd); + return; + } + SetFocus(wnd); + searchAddress = curAddress_ + 1; + } else { + searchAddress = matchAddress_ + 1; + } + + std::vector searchData; + if (!ParseSearchString(searchQuery_, !asciiSelected_, searchData)) { + statusMessage_ = "Invalid search text."; + return; + } + + std::vector> memoryAreas; + // Ignore the video memory mirrors. + memoryAreas.emplace_back(PSP_GetVidMemBase(), 0x04200000); + memoryAreas.emplace_back(PSP_GetKernelMemoryBase(), PSP_GetUserMemoryEnd()); + memoryAreas.emplace_back(PSP_GetScratchpadMemoryBase(), PSP_GetScratchpadMemoryEnd()); + + searching_ = true; + redraw(); // so the cursor is disabled + + for (size_t i = 0; i < memoryAreas.size(); i++) { + segmentStart = memoryAreas[i].first; + segmentEnd = memoryAreas[i].second; + + // better safe than sorry, I guess + if (!Memory::IsValidAddress(segmentStart)) + continue; + const u8 *dataPointer = Memory::GetPointerUnchecked(segmentStart); + + if (searchAddress < segmentStart) + searchAddress = segmentStart; + if (searchAddress >= segmentEnd) + continue; + + int index = searchAddress - segmentStart; + int endIndex = segmentEnd - segmentStart - (int)searchData.size(); + + while (index < endIndex) { + // cancel search + if ((index % 256) == 0 && ImGui::IsKeyDown(ImGuiKey_Escape)) { + searching_ = false; + return; + } + if (memcmp(&dataPointer[index], searchData.data(), searchData.size()) == 0) { + matchAddress_ = index + segmentStart; + searching_ = false; + gotoAddr(matchAddress_); + return; + } + index++; + } + } + + statusMessage_ = "Not found"; + searching_ = false; + redraw(); +} + +void ImMemView::drawOffsetScale(ImDrawList *drawList) { + int currentX = addressStartX_; + + drawList->AddText(ImVec2(currentX, offsetPositionY_), 0xFF600000, "Offset"); + // SetTextColor(hdc, 0x600000); + // TextOutA(hdc, currentX, offsetPositionY_, "Offset", 6); + + // the start offset, the size of the hex addresses and one space + currentX = addressStartX_ + ((8 + 1) * charWidth_); + + char temp[64]; + for (int i = 0; i < 16; i++) { + snprintf(temp, sizeof(temp), "%02X", i); + drawList->AddText(ImVec2(currentX, offsetPositionY_), 0xFF600000, temp); + currentX += 3 * charWidth_; // hex and space + } +} + +void ImMemView::toggleOffsetScale(CommonToggles toggle) { + if (toggle == On) + displayOffsetScale_ = true; + else if (toggle == Off) + displayOffsetScale_ = false; + + updateStatusBarText(); + redraw(); +} + +void ImMemView::setHighlightType(MemBlockFlags flags) { + if (highlightFlags_ != flags) { + highlightFlags_ = flags; + updateStatusBarText(); + redraw(); + } +} + +uint32_t ImMemView::pickTagColor(const std::string &tag) { + int colors[6] = { 0xe0FFFF, 0xFFE0E0, 0xE8E8FF, 0xFFE0FF, 0xE0FFE0, 0xFFFFE0 }; + int which = XXH3_64bits(tag.c_str(), tag.length()) % ARRAY_SIZE(colors); + return colors[which]; +} diff --git a/UI/ImDebugger/ImMemView.h b/UI/ImDebugger/ImMemView.h new file mode 100644 index 000000000000..691a1d7a5223 --- /dev/null +++ b/UI/ImDebugger/ImMemView.h @@ -0,0 +1,116 @@ +#pragma once + + +#include +#include +#include "ext/imgui/imgui.h" + +#include "Core/Debugger/DebugInterface.h" +#include "Core/Debugger/MemBlockInfo.h" + +enum OffsetSpacing { + offsetSpace = 3, // the number of blank lines that should be left to make space for the offsets + offsetLine = 1, // the line on which the offsets should be written +}; + +enum CommonToggles { + On, + Off, +}; + +class ImMemView { +public: + ImMemView(); + ~ImMemView(); + + void setDebugger(DebugInterface *deb) { + debugger_ = deb; + } + DebugInterface *getDebugger() { + return debugger_; + } + std::vector searchString(const std::string &searchQuery); + void Draw(ImDrawList *drawList); + // void onVScroll(WPARAM wParam, LPARAM lParam); + // void onKeyDown(WPARAM wParam, LPARAM lParam); + void onChar(int c); + void onMouseDown(float x, float y, int button); + void onMouseUp(float x, float y, int button); + void onMouseMove(float x, float y, int button); + void redraw(); + void gotoAddr(unsigned int addr); + + void drawOffsetScale(ImDrawList *drawList); + void toggleOffsetScale(CommonToggles toggle); + void setHighlightType(MemBlockFlags flags); + +private: + void ProcessKeyboardShortcuts(bool focused); + + bool ParseSearchString(const std::string &query, bool asHex, std::vector &data); + void updateStatusBarText(); + void search(bool continueSearch); + uint32_t pickTagColor(const std::string &tag); + + enum class GotoMode { + RESET, + RESET_IF_OUTSIDE, + FROM_CUR, + EXTEND, + }; + static GotoMode GotoModeFromModifiers(bool isRightClick); + void UpdateSelectRange(uint32_t target, GotoMode mode); + void GotoPoint(int x, int y, GotoMode mode); + void ScrollWindow(int lines, GotoMode mdoe); + void ScrollCursor(int bytes, GotoMode mdoe); + + static wchar_t szClassName[]; + DebugInterface *debugger_ = nullptr; + + // Whether to draw things using focused styles. + bool hasFocus_ = false; + MemBlockFlags highlightFlags_ = MemBlockFlags::ALLOC; + + // Current cursor position. + uint32_t curAddress_ = 0; + // Selected range, which should always be around the cursor. + uint32_t selectRangeStart_ = 0; + uint32_t selectRangeEnd_ = 0; + // Last select reset position, for selecting ranges. + uint32_t lastSelectReset_ = 0; + // Address of the first displayed byte. + uint32_t windowStart_ = 0; + // Number of bytes displayed per row. + int rowSize_ = 16; + + // Width of one monospace character (to maintain grid.) + int charWidth_ = 0; + // Height of one row of bytes. + int rowHeight_ = 0; + // Y position of offset header (at top.) + int offsetPositionY_; + // X position of addresses (at left.) + int addressStartX_ = 0; + // X position of hex display. + int hexStartX_ = 0; + // X position of text display. + int asciiStartX_ = 0; + // Whether cursor is within text display or hex display. + bool asciiSelected_ = false; + // Which nibble is selected, if in hex display. 0 means leftmost, i.e. most significant. + int selectedNibble_ = 0; + + bool displayOffsetScale_ = false; + + // Number of rows visible as of last redraw. + int visibleRows_ = 0; + + // Last used search query, used when continuing a search. + std::string searchQuery_; + // Address of last match when continuing search. + uint32_t matchAddress_ = 0xFFFFFFFF; + // Whether a search is in progress. + bool searching_ = false; + + std::string statusMessage_; +}; diff --git a/UI/UI.vcxproj b/UI/UI.vcxproj index 48f672fd7ff4..741a3406f600 100644 --- a/UI/UI.vcxproj +++ b/UI/UI.vcxproj @@ -54,6 +54,7 @@ + @@ -98,6 +99,7 @@ + diff --git a/UI/UI.vcxproj.filters b/UI/UI.vcxproj.filters index 9d9583c5f6e0..45b9a0ff0064 100644 --- a/UI/UI.vcxproj.filters +++ b/UI/UI.vcxproj.filters @@ -110,6 +110,9 @@ ImDebugger + + ImDebugger + @@ -220,6 +223,9 @@ ImDebugger + + ImDebugger + diff --git a/UWP/UI_UWP/UI_UWP.vcxproj b/UWP/UI_UWP/UI_UWP.vcxproj index b905828f7049..7d73b4443965 100644 --- a/UWP/UI_UWP/UI_UWP.vcxproj +++ b/UWP/UI_UWP/UI_UWP.vcxproj @@ -129,6 +129,7 @@ + @@ -173,6 +174,7 @@ + diff --git a/UWP/UI_UWP/UI_UWP.vcxproj.filters b/UWP/UI_UWP/UI_UWP.vcxproj.filters index a731a9744bfc..9103eefc7051 100644 --- a/UWP/UI_UWP/UI_UWP.vcxproj.filters +++ b/UWP/UI_UWP/UI_UWP.vcxproj.filters @@ -51,6 +51,9 @@ ImDebugger + + ImDebugger + @@ -103,6 +106,9 @@ ImDebugger + + ImDebugger + diff --git a/Windows/Debugger/CtrlMemView.h b/Windows/Debugger/CtrlMemView.h index 3c57ecb2aac6..e006e531e9cc 100644 --- a/Windows/Debugger/CtrlMemView.h +++ b/Windows/Debugger/CtrlMemView.h @@ -1,16 +1,10 @@ #pragma once ////////////////////////////////////////////////////////////////////////// -//CtrlDisAsmView -// CtrlDisAsmView.cpp +// CtrlMemView ////////////////////////////////////////////////////////////////////////// -//This Win32 control is made to be flexible and usable with -//every kind of CPU architecture that has fixed width instruction words. -//Just supply it an instance of a class derived from Debugger, with all methods -//overridden for full functionality. -// //To add to a dialog box, just draw a User Control in the dialog editor, -//and set classname to "CtrlDisAsmView". you also need to call CtrlDisAsmView::init() +//and set classname to "CtrlMemView". you also need to call CtrlMemView::init() //before opening this dialog, to register the window class. // //To get a class instance to be able to access it, just use getFrom(HWND wnd). diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 786b26716915..10ab9a5f77a3 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -879,6 +879,7 @@ LOCAL_SRC_FILES := \ $(SRC)/UI/ImDebugger/ImDebugger.cpp \ $(SRC)/UI/ImDebugger/ImGe.cpp \ $(SRC)/UI/ImDebugger/ImDisasmView.cpp \ + $(SRC)/UI/ImDebugger/ImMemView.cpp \ $(SRC)/UI/ImDebugger/ImStructViewer.cpp \ $(SRC)/UI/AudioCommon.cpp \ $(SRC)/UI/BackgroundAudio.cpp \ From 20c19f96e0bc6c11114bd6c57e1b2864ae835048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 1 Dec 2024 20:31:23 +0100 Subject: [PATCH 14/58] More memview work --- UI/ImDebugger/ImDebugger.cpp | 31 +++++- UI/ImDebugger/ImDebugger.h | 30 +++++ UI/ImDebugger/ImDisasmView.cpp | 85 ++++++--------- UI/ImDebugger/ImDisasmView.h | 26 ++--- UI/ImDebugger/ImMemView.cpp | 194 ++++++++++++--------------------- 5 files changed, 173 insertions(+), 193 deletions(-) diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 520da9c5027c..9a6b58db6a49 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -951,6 +951,12 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu ImGui::MenuItem("Callstacks", nullptr, &cfg_.callstackOpen); ImGui::MenuItem("Breakpoints", nullptr, &cfg_.breakpointsOpen); ImGui::MenuItem("Scheduler", nullptr, &cfg_.schedulerOpen); + ImGui::Separator(); + for (int i = 0; i < 4; i++) { + char title[64]; + snprintf(title, sizeof(title), "Memory %d", i + 1); + ImGui::MenuItem("Memory", nullptr, &cfg_.memViewOpen[i]); + } ImGui::EndMenu(); } if (ImGui::BeginMenu("HLE")) { @@ -1074,6 +1080,10 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu DrawSchedulerView(cfg_); } + for (int i = 0; i < 4; i++) { + mem_[i].Draw(mipsDebug, &cfg_.memViewOpen[i], i); + } + // Process UI commands switch (control.command.cmd) { case ImCmd::SHOW_IN_CPU_DISASM: @@ -1086,13 +1096,27 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu } void ImDebugger::Snapshot() { +} +void ImMemWindow::Draw(MIPSDebugInterface *mipsDebug, bool *open, int index) { + char title[256]; + snprintf(title, sizeof(title), "Memory %d", index); + ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin(title, open, ImGuiWindowFlags_NoNavInputs)) { + ImGui::End(); + return; + } + + memView_.setDebugger(mipsDebug); + memView_.Draw(ImGui::GetWindowDrawList()); + + ImGui::End(); } void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, CoreState coreState) { char title[256]; snprintf(title, sizeof(title), "%s - Disassembly", "Allegrex MIPS"); - + disasmView_.setDebugger(mipsDebug); ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); @@ -1347,6 +1371,11 @@ void ImConfig::SyncConfig(IniFile *ini, bool save) { sync.Sync("geDebuggerOpen", &geDebuggerOpen, false); sync.Sync("geStateOpen", &geStateOpen, false); sync.Sync("schedulerOpen", &schedulerOpen, false); + for (int i = 0; i < 4; i++) { + char name[64]; + snprintf(name, sizeof(name), "memory%dOpen", i + 1); + sync.Sync(name, &memViewOpen[i], false); + } sync.SetSection(ini->GetOrCreateSection("Settings")); sync.Sync("displayLatched", &displayLatched, false); diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h index 036b6162ac7f..c40c6841b107 100644 --- a/UI/ImDebugger/ImDebugger.h +++ b/UI/ImDebugger/ImDebugger.h @@ -16,6 +16,7 @@ #include "Core/Debugger/DebugInterface.h" #include "UI/ImDebugger/ImDisasmView.h" +#include "UI/ImDebugger/ImMemView.h" #include "UI/ImDebugger/ImStructViewer.h" #include "UI/ImDebugger/ImGe.h" @@ -58,6 +59,33 @@ class ImDisasmWindow { char searchTerm_[64]{}; }; +// Corresponds to the CMemView dialog +class ImMemWindow { +public: + void Draw(MIPSDebugInterface *mipsDebug, bool *open, int index); + ImMemView &View() { + return memView_; + } + void DirtySymbolMap() { + symsDirty_ = true; + } + +private: + // We just keep the state directly in the window. Can refactor later. + enum { + INVALID_ADDR = 0xFFFFFFFF, + }; + + // Symbol cache + std::vector symCache_; + bool symsDirty_ = true; + int selectedSymbol_ = -1; + char selectedSymbolName_[128]; + + ImMemView memView_; + char searchTerm_[64]{}; +}; + struct ImConfig { // Defaults for saved settings are set in SyncConfig. @@ -82,6 +110,7 @@ struct ImConfig { bool geDebuggerOpen; bool geStateOpen; bool schedulerOpen; + bool memViewOpen[4]; // HLE explorer settings // bool filterByUsed = true; @@ -139,6 +168,7 @@ class ImDebugger { ImDisasmWindow disasm_; ImGeDebuggerWindow geDebugger_; ImGeStateWindow geStateWindow_; + ImMemWindow mem_[4]; // We support 4 separate instances of the memory viewer. ImStructViewer structViewer_; // Open variables. diff --git a/UI/ImDebugger/ImDisasmView.cpp b/UI/ImDebugger/ImDisasmView.cpp index 48d1a123ceda..4bd26b766240 100644 --- a/UI/ImDebugger/ImDisasmView.cpp +++ b/UI/ImDebugger/ImDisasmView.cpp @@ -1,9 +1,9 @@ - #include "ext/imgui/imgui_internal.h" #include "ext/imgui/imgui_impl_thin3d.h" #include "Common/StringUtils.h" #include "Common/Log.h" +#include "Common/Math/geom2d.h" #include "Core/Core.h" #include "Core/Debugger/DebugInterface.h" #include "Core/Debugger/DisassemblyManager.h" @@ -82,24 +82,6 @@ bool ImDisasmView::getDisasmAddressText(u32 address, char* dest, bool abbreviate } } -// TODO: Replace with another impl. -/* -static std::string trimString(std::string input) { - size_t pos = input.find_first_not_of(" \t"); - if (pos != 0 && pos != std::string::npos) { - input = input.erase(0, pos); - } - - pos = input.find_last_not_of(" \t"); - if (pos != std::string::npos) { - size_t size = input.length() - pos - 1; - input = input.erase(pos + 1, size); - } - - return input; -} -*/ - void ImDisasmView::assembleOpcode(u32 address, const std::string &defaultText) { /* if (!Core_IsStepping()) { @@ -157,7 +139,7 @@ void ImDisasmView::assembleOpcode(u32 address, const std::string &defaultText) { */ } -void ImDisasmView::drawBranchLine(ImDrawList *drawList, Rect rect, std::map &addressPositions, const BranchLine &line) { +void ImDisasmView::drawBranchLine(ImDrawList *drawList, Bounds rect, std::map &addressPositions, const BranchLine &line) { u32 windowEnd = manager.getNthNextAddress(windowStart_, visibleRows_); float topY; @@ -165,7 +147,7 @@ void ImDisasmView::drawBranchLine(ImDrawList *drawList, Rect rect, std::map= windowEnd) { - topY = rect.bottom + 1.0f; + topY = rect.y2() + 1.0f; } else { topY = (float)addressPositions[line.first] + rowHeight_ / 2; } @@ -173,12 +155,12 @@ void ImDisasmView::drawBranchLine(ImDrawList *drawList, Rect rect, std::map= windowEnd) { - bottomY = rect.bottom + 1.0f; + bottomY = rect.y2() + 1.0f; } else { bottomY = (float)addressPositions[line.second] + rowHeight_ / 2; } - if ((topY < 0 && bottomY < 0) || (topY > rect.bottom && bottomY > rect.bottom)) { + if ((topY < 0 && bottomY < 0) || (topY > rect.y2() && bottomY > rect.y2())) { return; } @@ -200,7 +182,7 @@ void ImDisasmView::drawBranchLine(ImDrawList *drawList, Rect rect, std::mapAddLine(ImVec2(rect.left + curX, rect.top + curY), ImVec2(rect.left + (float)x, rect.top + (float)y), pen, 1.0f); + drawList->AddLine(ImVec2(rect.x + curX, rect.y + curY), ImVec2(rect.x + (float)x, rect.y + (float)y), pen, 1.0f); curX = x; curY = y; }; @@ -215,10 +197,10 @@ void ImDisasmView::drawBranchLine(ImDrawList *drawList, Rect rect, std::map rect.bottom) {// second is not visible, but first is + } else if (bottomY > rect.y2()) {// second is not visible, but first is moveTo(x - 2.f, topY); lineTo(x + 2.f, topY); - lineTo(x + 2.f, rect.bottom); + lineTo(x + 2.f, rect.y2()); if (line.type == LINE_UP) { moveTo(x, topY - 4.f); @@ -266,13 +248,13 @@ std::set ImDisasmView::getSelectedLineArguments() { return args; } -void ImDisasmView::drawArguments(ImDrawList *drawList, Rect rc, const DisassemblyLineInfo &line, float x, float y, ImColor textColor, const std::set ¤tArguments) { +void ImDisasmView::drawArguments(ImDrawList *drawList, Bounds rc, const DisassemblyLineInfo &line, float x, float y, ImColor textColor, const std::set ¤tArguments) { if (line.params.empty()) { return; } // Don't highlight the selected lines. if (isInInterval(selectRangeStart_, selectRangeEnd_ - selectRangeStart_, line.info.opcodeAddress)) { - drawList->AddText(ImVec2((float)(rc.left + x), (float)(rc.top + y)), textColor, line.params.data(), line.params.data() + line.params.size()); + drawList->AddText(ImVec2((float)(rc.x + x), (float)(rc.y + y)), textColor, line.params.data(), line.params.data() + line.params.size()); return; } @@ -286,7 +268,7 @@ void ImDisasmView::drawArguments(ImDrawList *drawList, Rect rc, const Disassembl ImColor curColor = textColor; auto Print = [&](std::string_view text) { - drawList->AddText(ImVec2(rc.left + curX, rc.top + curY), curColor, text.data(), text.data() + text.size()); + drawList->AddText(ImVec2(rc.x + curX, rc.y + curY), curColor, text.data(), text.data() + text.size()); ImVec2 sz = ImGui::CalcTextSize(text.data(), text.data() + text.size(), false, -1.0f); curX += sz.x; }; @@ -314,7 +296,7 @@ void ImDisasmView::drawArguments(ImDrawList *drawList, Rect rc, const Disassembl } void ImDisasmView::Draw(ImDrawList *drawList) { - if (!debugger->isAlive()) { + if (!debugger_->isAlive()) { return; } @@ -340,7 +322,7 @@ void ImDisasmView::Draw(ImDrawList *drawList) { } ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelY); - ImVec2 canvas_p1 = ImVec2(canvas_p0.x + canvas_sz.x, canvas_p0.y + canvas_sz.y); + const ImVec2 canvas_p1 = ImVec2(canvas_p0.x + canvas_sz.x, canvas_p0.y + canvas_sz.y); drawList->PushClipRect(canvas_p0, canvas_p1, true); drawList->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(25, 25, 25, 255)); @@ -348,15 +330,15 @@ void ImDisasmView::Draw(ImDrawList *drawList) { drawList->AddRect(canvas_p0, canvas_p1, IM_COL32(255, 255, 255, 255)); } - Rect rect; - rect.left = canvas_p0.x; - rect.top = canvas_p0.y; - rect.right = canvas_p1.x; - rect.bottom = canvas_p1.y; + Bounds bounds; + bounds.x = canvas_p0.x; + bounds.y = canvas_p0.y; + bounds.w = canvas_p1.x - canvas_p0.x; + bounds.h = canvas_p1.y - canvas_p0.y; calculatePixelPositions(); - visibleRows_ = (int)((rect.bottom - rect.top + rowHeight_ - 1.f) / rowHeight_); + visibleRows_ = (int)((bounds.h + rowHeight_ - 1.f) / rowHeight_); unsigned int address = windowStart_; std::map addressPositions; @@ -364,7 +346,7 @@ void ImDisasmView::Draw(ImDrawList *drawList) { const std::set currentArguments = getSelectedLineArguments(); DisassemblyLineInfo line; - const u32 pc = debugger->GetPC(); + const u32 pc = debugger_->GetPC(); for (int i = 0; i < visibleRows_; i++) { manager.getLine(address, displaySymbols_, line); @@ -375,7 +357,7 @@ void ImDisasmView::Draw(ImDrawList *drawList) { addressPositions[address] = rowY1; // draw background - ImColor backgroundColor = ImColor(0xFF000000 | debugger->getColor(address, true)); + ImColor backgroundColor = ImColor(0xFF000000 | debugger_->getColor(address, true)); ImColor textColor = 0xFFFFFFFF; if (isInInterval(address, line.totalSize, pc)) { @@ -391,7 +373,7 @@ void ImDisasmView::Draw(ImDrawList *drawList) { } } - drawList->AddRectFilled(ImVec2(rect.left, rect.top + rowY1), ImVec2(rect.right, rect.top + rowY1 + rowHeight_), backgroundColor); + drawList->AddRectFilled(ImVec2(bounds.x, bounds.y + rowY1), ImVec2(bounds.x2(), bounds.y + rowY1 + rowHeight_), backgroundColor); // display breakpoint, if any bool enabled; @@ -405,7 +387,7 @@ void ImDisasmView::Draw(ImDrawList *drawList) { char addressText[64]; getDisasmAddressText(address, addressText, true, line.type == DISTYPE_OPCODE); - drawList->AddText(ImVec2(rect.left + pixelPositions_.addressStart, rect.top + rowY1 + 2), textColor, addressText); + drawList->AddText(ImVec2(bounds.x + pixelPositions_.addressStart, bounds.y + rowY1 + 2), textColor, addressText); if (isInInterval(address, line.totalSize, pc)) { // Show the current PC with a little triangle. @@ -421,18 +403,18 @@ void ImDisasmView::Draw(ImDrawList *drawList) { line.params += line.info.conditionMet ? " ; true" : " ; false"; } - drawArguments(drawList, rect, line, pixelPositions_.argumentsStart, rowY1 + 2.f, textColor, currentArguments); + drawArguments(drawList, bounds, line, pixelPositions_.argumentsStart, rowY1 + 2.f, textColor, currentArguments); // The actual opcode. // Should be bold! - drawList->AddText(ImVec2(rect.left + pixelPositions_.opcodeStart, rect.top + rowY1 + 2.f), textColor, line.name.c_str()); + drawList->AddText(ImVec2(bounds.x + pixelPositions_.opcodeStart, bounds.y + rowY1 + 2.f), textColor, line.name.c_str()); address += line.totalSize; } std::vector branchLines = manager.getBranchLines(windowStart_, address - windowStart_); for (size_t i = 0; i < branchLines.size(); i++) { - drawBranchLine(drawList, rect, addressPositions, branchLines[i]); + drawBranchLine(drawList, bounds, addressPositions, branchLines[i]); } ImGuiIO& io = ImGui::GetIO(); @@ -481,7 +463,7 @@ void ImDisasmView::Draw(ImDrawList *drawList) { // INFO_LOG(Log::System, "Clicked %f,%f", mousePos.x, mousePos.y); if (mousePos.x < rowHeight_) { // Left column // Toggle breakpoint at dragAddr_. - debugger->toggleBreakpoint(curAddress_); + debugger_->toggleBreakpoint(curAddress_); bpPopup_ = true; } else { // disasmView_.selectedAddr_ = dragAddr_; @@ -528,7 +510,8 @@ void ImDisasmView::FollowBranch() { } void ImDisasmView::onChar(int c) { - if (keyTaken) return; + if (keyTaken) + return; char str[2]; str[0] = c; @@ -758,14 +741,14 @@ void ImDisasmView::CopyInstructions(u32 startAddr, u32 endAddr, CopyInstructions _assert_msg_((startAddr & 3) == 0, "readMemory() can't handle unaligned reads"); if (mode != CopyInstructionsMode::DISASM) { - int instructionSize = debugger->getInstructionSize(0); + int instructionSize = debugger_->getInstructionSize(0); int count = (endAddr - startAddr) / instructionSize; int space = count * 32; char *temp = new char[space]; char *p = temp, *end = temp + space; for (u32 pos = startAddr; pos < endAddr && p < end; pos += instructionSize) { - u32 data = mode == CopyInstructionsMode::OPCODES ? debugger->readMemory(pos) : pos; + u32 data = mode == CopyInstructionsMode::OPCODES ? debugger_->readMemory(pos) : pos; p += snprintf(p, end - p, "%08X", data); // Don't leave a trailing newline. @@ -812,7 +795,7 @@ void ImDisasmView::PopupMenu() { ImGui::Separator(); if (ImGui::MenuItem("Set PC to here")) { - debugger->SetPC(curAddress_); + debugger_->SetPC(curAddress_); } if (ImGui::MenuItem("Follow branch")) { FollowBranch(); @@ -1112,8 +1095,8 @@ std::string ImDisasmView::disassembleRange(u32 start, u32 size) { // gather all branch targets without labels std::set branchAddresses; - for (u32 i = 0; i < size; i += debugger->getInstructionSize(0)) { - MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(debugger, start + i); + for (u32 i = 0; i < size; i += debugger_->getInstructionSize(0)) { + MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(debugger_, start + i); if (info.isBranch && g_symbolMap->GetLabelString(info.branchTarget).empty()) { if (branchAddresses.find(info.branchTarget) == branchAddresses.end()) { diff --git a/UI/ImDebugger/ImDisasmView.h b/UI/ImDebugger/ImDisasmView.h index 00906c45cbea..3cbd51572054 100644 --- a/UI/ImDebugger/ImDisasmView.h +++ b/UI/ImDebugger/ImDisasmView.h @@ -8,6 +8,7 @@ #include "ext/imgui/imgui.h" #include "Common/CommonTypes.h" +#include "Common/Math/geom2d.h" #include "Core/Debugger/DisassemblyManager.h" #include "Core/Debugger/DebugInterface.h" @@ -43,14 +44,14 @@ class ImDisasmView { u32 yToAddress(float y); void setDebugger(DebugInterface *deb) { - if (debugger != deb) { - debugger = deb; - curAddress_ = debugger->GetPC(); + if (debugger_ != deb) { + debugger_ = deb; + curAddress_ = debugger_->GetPC(); manager.setCpu(deb); } } DebugInterface *getDebugger() { - return debugger; + return debugger_; } void scrollStepping(u32 newPc); @@ -70,10 +71,10 @@ class ImDisasmView { ScanVisibleFunctions(); } void gotoPC() { - gotoAddr(debugger->GetPC()); + gotoAddr(debugger_->GetPC()); } void gotoLR() { - gotoAddr(debugger->GetLR()); + gotoAddr(debugger_->GetLR()); } u32 getSelection() { return curAddress_; @@ -120,13 +121,6 @@ class ImDisasmView { ADDRESSES, }; - struct Rect { - float left; - float top; - float right; - float bottom; - }; - void ProcessKeyboardShortcuts(bool focused); void assembleOpcode(u32 address, const std::string &defaultText); std::string disassembleRange(u32 start, u32 size); @@ -135,11 +129,11 @@ class ImDisasmView { void calculatePixelPositions(); bool getDisasmAddressText(u32 address, char* dest, bool abbreviateLabels, bool showData); void updateStatusBarText(); - void drawBranchLine(ImDrawList *list, ImDisasmView::Rect rc, std::map &addressPositions, const BranchLine &line); + void drawBranchLine(ImDrawList *list, Bounds rc, std::map &addressPositions, const BranchLine &line); void CopyInstructions(u32 startAddr, u32 endAddr, CopyInstructionsMode mode); void NopInstructions(u32 startAddr, u32 endAddr); std::set getSelectedLineArguments(); - void drawArguments(ImDrawList *list, ImDisasmView::Rect rc, const DisassemblyLineInfo &line, float x, float y, ImColor textColor, const std::set ¤tArguments); + void drawArguments(ImDrawList *list, Bounds rc, const DisassemblyLineInfo &line, float x, float y, ImColor textColor, const std::set ¤tArguments); DisassemblyManager manager; u32 curAddress_ = 0; @@ -152,7 +146,7 @@ class ImDisasmView { bool hasFocus_ = true; bool showHex_ = false; - DebugInterface *debugger = nullptr; + DebugInterface *debugger_ = nullptr; u32 windowStart_ = 0; int visibleRows_ = 1; diff --git a/UI/ImDebugger/ImMemView.cpp b/UI/ImDebugger/ImMemView.cpp index 406fd0390a73..b455b6ec0c73 100644 --- a/UI/ImDebugger/ImMemView.cpp +++ b/UI/ImDebugger/ImMemView.cpp @@ -4,6 +4,7 @@ #include #include "ext/imgui/imgui.h" +#include "ext/imgui/imgui_internal.h" #include "ext/imgui/imgui_impl_thin3d.h" #include "ext/xxhash.h" @@ -38,103 +39,58 @@ ImMemView::~ImMemView() {} /* LRESULT CALLBACK ImMemView::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { - ImMemView *ccp = ImMemView::getFrom(hwnd); - static bool lmbDown = false, rmbDown = false; - - switch (msg) { - case WM_NCCREATE: - // Allocate a new CustCtrl structure for this window. - ccp = new ImMemView(hwnd); - - // Continue with window creation. - return ccp != NULL; - - // Clean up when the window is destroyed. - case WM_NCDESTROY: - delete ccp; - break; - case WM_SETFONT: - break; - case WM_SIZE: - ccp->redraw(); - break; - case WM_PAINT: - ccp->onPaint(wParam, lParam); - break; - case WM_VSCROLL: - ccp->onVScroll(wParam, lParam); - break; - case WM_MOUSEWHEEL: if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) { ccp->ScrollWindow(-3, GotoModeFromModifiers(false)); } else if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) { ccp->ScrollWindow(3, GotoModeFromModifiers(false)); } - break; - case WM_ERASEBKGND: - return FALSE; - case WM_KEYDOWN: - ccp->onKeyDown(wParam, lParam); - return 0; - case WM_CHAR: - ccp->onChar(wParam, lParam); - return 0; - case WM_KEYUP: - return 0; - case WM_LBUTTONDOWN: SetFocus(hwnd); lmbDown = true; ccp->onMouseDown(wParam, lParam, 1); break; - case WM_RBUTTONDOWN: SetFocus(hwnd); rmbDown = true; ccp->onMouseDown(wParam, lParam, 2); break; - case WM_MOUSEMOVE: ccp->onMouseMove(wParam, lParam, (lmbDown ? 1 : 0) | (rmbDown ? 2 : 0)); break; - case WM_LBUTTONUP: lmbDown = false; ccp->onMouseUp(wParam, lParam, 1); break; - case WM_RBUTTONUP: rmbDown = false; ccp->onMouseUp(wParam, lParam, 2); break; - case WM_SETFOCUS: - SetFocus(hwnd); - ccp->hasFocus_ = true; - ccp->redraw(); - break; - case WM_KILLFOCUS: - ccp->hasFocus_ = false; - ccp->redraw(); - break; - case WM_GETDLGCODE: // we want to process the arrow keys and all characters ourselves - return DLGC_WANTARROWS | DLGC_WANTCHARS | DLGC_WANTTAB; - break; - case WM_TIMER: - // This is actually delayed too, using another timer. That way we won't update twice. - if (wParam == IDT_REDRAW_AUTO && IsWindowVisible(ccp->wnd)) - ccp->redraw(); - - if (wParam == IDT_REDRAW_DELAYED) { - InvalidateRect(hwnd, nullptr, FALSE); - UpdateWindow(hwnd); - ccp->redrawScheduled_ = false; - KillTimer(hwnd, wParam); - } - break; - default: - break; - } - return DefWindowProc(hwnd, msg, wParam, lParam); } */ void ImMemView::Draw(ImDrawList *drawList) { auto memLock = Memory::Lock(); + if (!debugger_->isAlive()) { + return; + } + + ImGui_PushFixedFont(); + + rowHeight_ = ImGui::GetTextLineHeightWithSpacing(); + charWidth_ = ImGui::CalcTextSize("W", nullptr, false, -1.0f).x; + + const ImVec2 canvas_p0 = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates! + const ImVec2 canvas_sz = ImGui::GetContentRegionAvail(); // Resize canvas to what's available + const ImVec2 canvas_p1 = ImVec2(canvas_p0.x + canvas_sz.x, canvas_p0.y + canvas_sz.y); - visibleRows_ = rect_.bottom / rowHeight_; + // This will catch our interactions + bool pressed = ImGui::InvisibleButton("canvas", canvas_sz, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); + const bool is_hovered = ImGui::IsItemHovered(); // Hovered + const bool is_active = ImGui::IsItemActive(); // Held + + if (pressed) { + // INFO_LOG(Log::System, "Pressed"); + } + ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelY); + + visibleRows_ = canvas_sz.y / rowHeight_; if (displayOffsetScale_) { // visibleRows_ is calculated based on the size of the control, but X rows have already been used for the offsets and are no longer usable visibleRows_ -= offsetSpace; } - ImGui_PushFixedFont(); + Bounds bounds; + bounds.x = canvas_p0.x; + bounds.y = canvas_p0.y; + bounds.w = canvas_p1.x - canvas_p0.x; + bounds.h = canvas_p1.y - canvas_p0.y; - // Background fill - Rectangle(hdc, 0, 0, rect_.right, rect_.bottom); + drawList->PushClipRect(canvas_p0, canvas_p1, true); + drawList->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(25, 25, 25, 255)); if (displayOffsetScale_) - drawOffsetScale(); + drawOffsetScale(drawList); std::vector memRangeInfo = FindMemInfoByFlag(highlightFlags_, windowStart_, (visibleRows_ + 1) * rowSize_); @@ -151,8 +107,7 @@ void ImMemView::Draw(ImDrawList *drawList) { uint32_t address = windowStart_ + i * rowSize_; snprintf(temp, sizeof(temp), "%08X", address); - setTextColors(0x600000, standardBG); - TextOutA(hdc, addressStartX_, rowY, temp, (int)strlen(temp)); + drawList->AddText(ImVec2(canvas_p0.x + addressStartX_, canvas_p0.y + rowY), IM_COL32(0, 0, 0x60, 0xFF), temp); union { uint32_t words[4]; @@ -189,16 +144,17 @@ void ImMemView::Draw(ImDrawList *drawList) { c = '.'; } - COLORREF hexBGCol = standardBG; - COLORREF hexTextCol = 0x000000; - COLORREF continueBGCol = standardBG; - COLORREF asciiBGCol = standardBG; - COLORREF asciiTextCol = 0x000000; + ImColor standardBG = 0xFF000000; + ImColor continueBGCol = 0xFF000000; + ImColor hexBGCol = 0xFF000000; + ImColor hexTextCol = 0xFFFFFFFF; + ImColor asciiBGCol = 0xFF000000; + ImColor asciiTextCol = 0xFFFFFFFF; int underline = -1; if (address + j >= selectRangeStart_ && address + j < selectRangeEnd_ && !searching_) { if (asciiSelected_) { - hexBGCol = 0xC0C0C0; + hexBGCol = ImColor(0xFFC0C0C0); hexTextCol = 0x000000; asciiBGCol = hasFocus_ ? 0xFF9933 : 0xC0C0C0; asciiTextCol = hasFocus_ ? 0xFFFFFF : 0x000000; @@ -220,35 +176,35 @@ void ImMemView::Draw(ImDrawList *drawList) { hexLen = tagContinues ? 3 : 2; } - setTextColors(hexTextCol, hexBGCol); + ImColor fg = hexTextCol; + ImColor bg = hexBGCol; if (underline >= 0) { - SelectObject(hdc, underline == 0 ? (HGDIOBJ)underlineFont : (HGDIOBJ)font); - TextOutA(hdc, hexX, rowY, &temp[0], 1); - SelectObject(hdc, underline == 0 ? (HGDIOBJ)font : (HGDIOBJ)underlineFont); - TextOutA(hdc, hexX + charWidth_, rowY, &temp[1], 1); - SelectObject(hdc, (HGDIOBJ)font); + // SelectObject(hdc, underline == 0 ? (HGDIOBJ)underlineFont : (HGDIOBJ)font); + drawList->AddText(ImVec2(canvas_p0.x + hexX, canvas_p0.y + rowY), fg, &temp[0], &temp[1]); + // SelectObject(hdc, underline == 0 ? (HGDIOBJ)font : (HGDIOBJ)underlineFont); + drawList->AddText(ImVec2(canvas_p0.x + hexX + charWidth_, canvas_p0.y + rowY), fg, &temp[1]); // If the tag keeps going, draw the BG too. if (continueBGCol != standardBG) { - setTextColors(0x000000, continueBGCol); - TextOutA(hdc, hexX + charWidth_ * 2, rowY, &temp[2], 1); + // setTextColors(0x000000, continueBGCol); + // TextOutA(hdc, hexX + charWidth_ * 2, rowY, &temp[2], 1); } } else { if (continueBGCol != hexBGCol) { - TextOutA(hdc, hexX, rowY, temp, 2); - setTextColors(0x000000, continueBGCol); - TextOutA(hdc, hexX + charWidth_ * 2, rowY, &temp[2], 1); + drawList->AddText(ImVec2(canvas_p0.x + hexX, canvas_p0.y + rowY), fg, &temp[0], &temp[2]); } else { - TextOutA(hdc, hexX, rowY, temp, hexLen); + drawList->AddText(ImVec2(canvas_p0.x + hexX, canvas_p0.y + rowY), fg, &temp[0], &temp[2]); } } - setTextColors(asciiTextCol, asciiBGCol); - TextOutA(hdc, asciiX, rowY, &c, 1); + // setTextColors(asciiTextCol, asciiBGCol); + // TextOutA(hdc, asciiX, rowY, &c, 1); + drawList->AddText(ImVec2(canvas_p0.x + asciiX, canvas_p0.y + rowY), fg, &c, &c + 1); } } ImGui_PopFont(); + drawList->PopClipRect(); } // We don't have a scroll bar - though maybe we should build one. @@ -282,11 +238,13 @@ void ImMemView::ProcessKeyboardShortcuts(bool focused) { if (io.KeyMods & ImGuiMod_Ctrl) { if (ImGui::IsKeyPressed(ImGuiKey_G)) { + /* u32 addr; if (executeExpressionWindow(wnd, debugger_, addr) == false) return; gotoAddr(addr); return; + */ } if (ImGui::IsKeyPressed(ImGuiKey_F)) { search(false); @@ -325,6 +283,7 @@ void ImMemView::onChar(int c) { if (io.KeyMods & ImGuiMod_Ctrl) { return; + } if (!Memory::IsValidAddress(curAddress_)) { ScrollCursor(1, GotoMode::RESET); @@ -385,9 +344,6 @@ void ImMemView::onMouseDown(WPARAM wParam, LPARAM lParam, int button) { } void ImMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) { - if (Achievements::HardcoreModeActive()) - return; - if (button == 2) { int32_t selectedSize = selectRangeEnd_ - selectRangeStart_; bool enable16 = !asciiSelected_ && (selectedSize == 1 || (selectedSize & 1) == 0); @@ -398,16 +354,13 @@ void ImMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) { EnableMenuItem(menu, ID_MEMVIEW_COPYVALUE_32, enable32 ? MF_ENABLED : MF_GRAYED); EnableMenuItem(menu, ID_MEMVIEW_COPYFLOAT_32, enable32 ? MF_ENABLED : MF_GRAYED); - switch (TriggerContextMenu(ContextMenuID::MEMVIEW, wnd, ContextPoint::FromEvent(lParam))) { - case ID_MEMVIEW_DUMP: - { + if (ImGui::MenuItem("Dump memory")) { DumpMemoryWindow dump(wnd, debugger_); dump.exec(); break; } - case ID_MEMVIEW_COPYVALUE_8: - { + if (ImGui::MenuItem("Copy value (8-bit)")) { auto memLock = Memory::Lock(); size_t tempSize = 3 * selectedSize + 1; char *temp = new char[tempSize]; @@ -431,13 +384,11 @@ void ImMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) { if (pos > temp) *(pos - 1) = '\0'; } - W32Util::CopyTextToClipboard(wnd, temp); + // W32Util::CopyTextToClipboard(wnd, temp); delete[] temp; } - break; - case ID_MEMVIEW_COPYVALUE_16: - { + if (ImGui::MenuItem("Copy value (16-bit)")) { auto memLock = Memory::Lock(); size_t tempSize = 5 * ((selectedSize + 1) / 2) + 1; char *temp = new char[tempSize]; @@ -455,10 +406,8 @@ void ImMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) { W32Util::CopyTextToClipboard(wnd, temp); delete[] temp; } - break; - case ID_MEMVIEW_COPYVALUE_32: - { + if (ImGui::MenuItem("Copy value (32-bit)")) { auto memLock = Memory::Lock(); size_t tempSize = 9 * ((selectedSize + 3) / 4) + 1; char *temp = new char[tempSize]; @@ -476,18 +425,15 @@ void ImMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) { W32Util::CopyTextToClipboard(wnd, temp); delete[] temp; } - break; - case ID_MEMVIEW_COPYFLOAT_32: - { + if (ImGui::MenuItem("Copy value (float32)") { auto memLock = Memory::Lock(); std::ostringstream stream; stream << (Memory::IsValidAddress(curAddress_) ? Memory::Read_Float(curAddress_) : NAN); auto temp_string = stream.str(); W32Util::CopyTextToClipboard(wnd, temp_string.c_str()); } - break; - + /* case ID_MEMVIEW_EXTENTBEGIN: { std::vector memRangeInfo = FindMemInfoByFlag(highlightFlags_, curAddress_, 1); @@ -509,28 +455,26 @@ void ImMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) { gotoAddr(addr); break; } - - case ID_MEMVIEW_COPYADDRESS: - { + */ + if (ImGui::MenuItem("Copy address")) { char temp[24]; snprintf(temp, sizeof(temp), "0x%08X", curAddress_); W32Util::CopyTextToClipboard(wnd, temp); } - break; - case ID_MEMVIEW_GOTOINDISASM: + if (ImGui::MenuItem("Goto in disasm")) { if (disasmWindow) { disasmWindow->Goto(curAddress_); disasmWindow->Show(true); } - break; } - return; } + /* int x = LOWORD(lParam); int y = HIWORD(lParam); ReleaseCapture(); + */ GotoPoint(x, y, GotoModeFromModifiers(button == 2)); } From adcfffd1d6fbdba298c6bdaf007a831e5f0318e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 10 Dec 2024 19:43:41 +0100 Subject: [PATCH 15/58] Fix memview build --- UI/ImDebugger/ImMemView.cpp | 83 +++++++++++++++++-------------------- UI/ImDebugger/ImMemView.h | 2 +- 2 files changed, 38 insertions(+), 47 deletions(-) diff --git a/UI/ImDebugger/ImMemView.cpp b/UI/ImDebugger/ImMemView.cpp index b455b6ec0c73..ff767343c919 100644 --- a/UI/ImDebugger/ImMemView.cpp +++ b/UI/ImDebugger/ImMemView.cpp @@ -17,7 +17,6 @@ #include "UI/ImDebugger/ImDebugger.h" #include "UI/ImDebugger/ImMemView.h" -// #include "DumpMemoryWindow.h" ImMemView::ImMemView() { const float fontScale = 1.0f / g_display.dpi_scale_real_y; @@ -49,7 +48,6 @@ LRESULT CALLBACK ImMemView::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM l */ void ImMemView::Draw(ImDrawList *drawList) { - auto memLock = Memory::Lock(); if (!debugger_->isAlive()) { return; } @@ -275,7 +273,6 @@ void ImMemView::ProcessKeyboardShortcuts(bool focused) { } void ImMemView::onChar(int c) { - auto memLock = Memory::Lock(); if (!PSP_IsInited()) return; @@ -322,10 +319,11 @@ void ImMemView::onChar(int c) { ImMemView::GotoMode ImMemView::GotoModeFromModifiers(bool isRightClick) { GotoMode mode = GotoMode::RESET; + auto &io = ImGui::GetIO(); if (isRightClick) { mode = GotoMode::RESET_IF_OUTSIDE; - } else if (KeyDownAsync(VK_SHIFT)) { - if (KeyDownAsync(VK_CONTROL)) + } else if (io.KeyMods & ImGuiMod_Shift) { + if (io.KeyMods & ImGuiMod_Ctrl) mode = GotoMode::EXTEND; else mode = GotoMode::FROM_CUR; @@ -333,35 +331,29 @@ ImMemView::GotoMode ImMemView::GotoModeFromModifiers(bool isRightClick) { return mode; } -void ImMemView::onMouseDown(WPARAM wParam, LPARAM lParam, int button) { +void ImMemView::onMouseDown(float x, float y, int button) { if (Achievements::HardcoreModeActive()) return; - int x = LOWORD(lParam); - int y = HIWORD(lParam); - GotoPoint(x, y, GotoModeFromModifiers(button == 2)); } -void ImMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) { - if (button == 2) { +void ImMemView::PopupMenu() { + if (ImGui::BeginPopup("context")) { + int32_t selectedSize = selectRangeEnd_ - selectRangeStart_; bool enable16 = !asciiSelected_ && (selectedSize == 1 || (selectedSize & 1) == 0); bool enable32 = !asciiSelected_ && (selectedSize == 1 || (selectedSize & 3) == 0); - HMENU menu = GetContextMenu(ContextMenuID::MEMVIEW); - EnableMenuItem(menu, ID_MEMVIEW_COPYVALUE_16, enable16 ? MF_ENABLED : MF_GRAYED); - EnableMenuItem(menu, ID_MEMVIEW_COPYVALUE_32, enable32 ? MF_ENABLED : MF_GRAYED); - EnableMenuItem(menu, ID_MEMVIEW_COPYFLOAT_32, enable32 ? MF_ENABLED : MF_GRAYED); - if (ImGui::MenuItem("Dump memory")) { + /* DumpMemoryWindow dump(wnd, debugger_); dump.exec(); break; + */ } if (ImGui::MenuItem("Copy value (8-bit)")) { - auto memLock = Memory::Lock(); size_t tempSize = 3 * selectedSize + 1; char *temp = new char[tempSize]; memset(temp, 0, tempSize); @@ -384,12 +376,12 @@ void ImMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) { if (pos > temp) *(pos - 1) = '\0'; } - // W32Util::CopyTextToClipboard(wnd, temp); + + System_CopyStringToClipboard(temp); delete[] temp; } if (ImGui::MenuItem("Copy value (16-bit)")) { - auto memLock = Memory::Lock(); size_t tempSize = 5 * ((selectedSize + 1) / 2) + 1; char *temp = new char[tempSize]; memset(temp, 0, tempSize); @@ -403,12 +395,11 @@ void ImMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) { if (pos > temp) *(pos - 1) = '\0'; - W32Util::CopyTextToClipboard(wnd, temp); + System_CopyStringToClipboard(temp); delete[] temp; } if (ImGui::MenuItem("Copy value (32-bit)")) { - auto memLock = Memory::Lock(); size_t tempSize = 9 * ((selectedSize + 3) / 4) + 1; char *temp = new char[tempSize]; memset(temp, 0, tempSize); @@ -422,16 +413,14 @@ void ImMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) { if (pos > temp) *(pos - 1) = '\0'; - W32Util::CopyTextToClipboard(wnd, temp); + System_CopyStringToClipboard(temp); delete[] temp; } - if (ImGui::MenuItem("Copy value (float32)") { - auto memLock = Memory::Lock(); - std::ostringstream stream; - stream << (Memory::IsValidAddress(curAddress_) ? Memory::Read_Float(curAddress_) : NAN); - auto temp_string = stream.str(); - W32Util::CopyTextToClipboard(wnd, temp_string.c_str()); + if (ImGui::MenuItem("Copy value (float32)")) { + char temp[64]; + snprintf(temp, sizeof(temp), "%f", Memory::IsValidAddress(curAddress_) ? Memory::Read_Float(curAddress_) : NAN); + System_CopyStringToClipboard(temp); } /* case ID_MEMVIEW_EXTENTBEGIN: @@ -459,15 +448,29 @@ void ImMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) { if (ImGui::MenuItem("Copy address")) { char temp[24]; snprintf(temp, sizeof(temp), "0x%08X", curAddress_); - W32Util::CopyTextToClipboard(wnd, temp); + System_CopyStringToClipboard(temp); } if (ImGui::MenuItem("Goto in disasm")) { + /* if (disasmWindow) { disasmWindow->Goto(curAddress_); disasmWindow->Show(true); } + */ } + ImGui::EndPopup(); + } +} + +void ImMemView::onMouseUp(float x, float y, int button) { + if (button == 2) { + + // HMENU menu = GetContextMenu(ContextMenuID::MEMVIEW); + // EnableMenuItem(menu, ID_MEMVIEW_COPYVALUE_16, enable16 ? MF_ENABLED : MF_GRAYED); + // EnableMenuItem(menu, ID_MEMVIEW_COPYVALUE_32, enable32 ? MF_ENABLED : MF_GRAYED); + // EnableMenuItem(menu, ID_MEMVIEW_COPYFLOAT_32, enable32 ? MF_ENABLED : MF_GRAYED); + } /* @@ -478,13 +481,10 @@ void ImMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) { GotoPoint(x, y, GotoModeFromModifiers(button == 2)); } -void ImMemView::onMouseMove(WPARAM wParam, LPARAM lParam, int button) { +void ImMemView::onMouseMove(float x, float y, int button) { if (Achievements::HardcoreModeActive()) return; - int x = LOWORD(lParam); - int y = HIWORD(lParam); - if (button & 1) { GotoPoint(x, y, GotoModeFromModifiers(button == 2)); } @@ -499,8 +499,7 @@ void ImMemView::updateStatusBarText() { for (MemBlockInfo info : memRangeInfo) { snprintf(text, sizeof(text), "%08X - %s %08X-%08X (at PC %08X / %lld ticks)", curAddress_, info.tag.c_str(), info.start, info.start + info.size, info.pc, info.ticks); } - - SendMessage(GetParent(wnd), WM_DEB_SETSTATUSBARTEXT, 0, (LPARAM)text); + statusMessage_ = text; } void ImMemView::UpdateSelectRange(uint32_t target, GotoMode mode) { @@ -546,7 +545,6 @@ void ImMemView::GotoPoint(int x, int y, GotoMode mode) { // ignore clicks on the offset space if (line < offsetSpace) { updateStatusBarText(); - redraw(); return; } // since each row has been written X rows down from where the window expected it to be written the target of the clicks must be adjusted @@ -583,14 +581,12 @@ void ImMemView::GotoPoint(int x, int y, GotoMode mode) { selectedNibble_ = targetNibble; asciiSelected_ = targetAscii; UpdateSelectRange(target, mode); - updateStatusBarText(); - redraw(); } } void ImMemView::gotoAddr(unsigned int addr) { - int lines = rect_.bottom / rowHeight_; + int lines = visibleRows_; u32 windowEnd = windowStart_ + lines * rowSize_; curAddress_ = addr; @@ -604,7 +600,6 @@ void ImMemView::gotoAddr(unsigned int addr) { } updateStatusBarText(); - redraw(); } void ImMemView::ScrollWindow(int lines, GotoMode mode) { @@ -613,7 +608,6 @@ void ImMemView::ScrollWindow(int lines, GotoMode mode) { UpdateSelectRange(curAddress_ + lines * rowSize_, mode); updateStatusBarText(); - redraw(); } void ImMemView::ScrollCursor(int bytes, GotoMode mode) { @@ -643,7 +637,6 @@ void ImMemView::ScrollCursor(int bytes, GotoMode mode) { } updateStatusBarText(); - redraw(); } bool ImMemView::ParseSearchString(const std::string &query, bool asHex, std::vector &data) { @@ -682,7 +675,6 @@ bool ImMemView::ParseSearchString(const std::string &query, bool asHex, std::vec std::vector ImMemView::searchString(const std::string &searchQuery) { std::vector searchResAddrs; - auto memLock = Memory::Lock(); if (!PSP_IsInited()) return searchResAddrs; @@ -719,7 +711,7 @@ std::vector ImMemView::searchString(const std::string &searchQuery) { }; void ImMemView::search(bool continueSearch) { - auto memLock = Memory::Lock(); + /* if (!PSP_IsInited()) return; @@ -788,6 +780,7 @@ void ImMemView::search(bool continueSearch) { statusMessage_ = "Not found"; searching_ = false; redraw(); + */ } void ImMemView::drawOffsetScale(ImDrawList *drawList) { @@ -815,14 +808,12 @@ void ImMemView::toggleOffsetScale(CommonToggles toggle) { displayOffsetScale_ = false; updateStatusBarText(); - redraw(); } void ImMemView::setHighlightType(MemBlockFlags flags) { if (highlightFlags_ != flags) { highlightFlags_ = flags; updateStatusBarText(); - redraw(); } } diff --git a/UI/ImDebugger/ImMemView.h b/UI/ImDebugger/ImMemView.h index 691a1d7a5223..1b22a44d21e3 100644 --- a/UI/ImDebugger/ImMemView.h +++ b/UI/ImDebugger/ImMemView.h @@ -37,7 +37,6 @@ class ImMemView { void onMouseDown(float x, float y, int button); void onMouseUp(float x, float y, int button); void onMouseMove(float x, float y, int button); - void redraw(); void gotoAddr(unsigned int addr); void drawOffsetScale(ImDrawList *drawList); @@ -63,6 +62,7 @@ class ImMemView { void GotoPoint(int x, int y, GotoMode mode); void ScrollWindow(int lines, GotoMode mdoe); void ScrollCursor(int bytes, GotoMode mdoe); + void PopupMenu(); static wchar_t szClassName[]; DebugInterface *debugger_ = nullptr; From 11e858f0f114541c8a167c1fa229fae28f8bafa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Dec 2024 17:00:57 +0100 Subject: [PATCH 16/58] Add input to memview. Use step counters to control updates. --- UI/ImDebugger/ImDebugger.cpp | 16 ++++++++++++++++ UI/ImDebugger/ImDebugger.h | 3 +++ UI/ImDebugger/ImDisasmView.cpp | 32 ++++++++++++-------------------- UI/ImDebugger/ImDisasmView.h | 2 +- UI/ImDebugger/ImGe.cpp | 9 ++++++++- UI/ImDebugger/ImGe.h | 2 ++ UI/ImDebugger/ImMemView.cpp | 33 +++++++++++++++++++++++++++++++++ 7 files changed, 75 insertions(+), 22 deletions(-) diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 9a6b58db6a49..000acb35c268 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -35,6 +35,7 @@ // GPU things #include "GPU/Common/GPUDebugInterface.h" +#include "GPU/Debugger/Stepping.h" #include "UI/ImDebugger/ImDebugger.h" #include "UI/ImDebugger/ImGe.h" @@ -879,6 +880,20 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu return; } + // Watch the step counters to figure out when to update things. + + if (lastCpuStepCount_ != Core_GetSteppingCounter()) { + lastCpuStepCount_ = Core_GetSteppingCounter(); + disasm_.View().NotifyStep(); + } + + if (lastGpuStepCount_ != GPUStepping::GetSteppingCounter()) { + // A GPU step has happened since last time. This means that we should re-center the cursor. + // Snapshot(); + lastGpuStepCount_ = GPUStepping::GetSteppingCounter(); + geDebugger_.View().NotifyStep(); + } + ImControl control{}; if (ImGui::BeginMainMenuBar()) { @@ -1096,6 +1111,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu } void ImDebugger::Snapshot() { + } void ImMemWindow::Draw(MIPSDebugInterface *mipsDebug, bool *open, int index) { diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h index c40c6841b107..880c4d95095b 100644 --- a/UI/ImDebugger/ImDebugger.h +++ b/UI/ImDebugger/ImDebugger.h @@ -171,6 +171,9 @@ class ImDebugger { ImMemWindow mem_[4]; // We support 4 separate instances of the memory viewer. ImStructViewer structViewer_; + int lastCpuStepCount_ = -1; + int lastGpuStepCount_ = -1; + // Open variables. ImConfig cfg_{}; }; diff --git a/UI/ImDebugger/ImDisasmView.cpp b/UI/ImDebugger/ImDisasmView.cpp index 4bd26b766240..0391d15cb53a 100644 --- a/UI/ImDebugger/ImDisasmView.cpp +++ b/UI/ImDebugger/ImDisasmView.cpp @@ -450,15 +450,6 @@ void ImDisasmView::Draw(ImDrawList *drawList) { ProcessKeyboardShortcuts(ImGui::IsItemFocused()); - int coreStep = Core_GetSteppingCounter(); - if (coreStep != lastSteppingCount_) { - // A step has happened since last time. This means that we should re-center the cursor. - if (followPC_) { - gotoPC(); - } - lastSteppingCount_ = coreStep; - } - if (pressed) { // INFO_LOG(Log::System, "Clicked %f,%f", mousePos.x, mousePos.y); if (mousePos.x < rowHeight_) { // Left column @@ -479,6 +470,12 @@ void ImDisasmView::Draw(ImDrawList *drawList) { drawList->PopClipRect(); } +void ImDisasmView::NotifyStep() { + if (followPC_) { + gotoPC(); + } +} + void ImDisasmView::ScrollRelative(int amount) { if (amount > 0) { windowStart_ = manager.getNthNextAddress(windowStart_, amount); @@ -763,16 +760,6 @@ void ImDisasmView::CopyInstructions(u32 startAddr, u32 endAddr, CopyInstructions } } -void ImDisasmView::NopInstructions(u32 selectRangeStart, u32 selectRangeEnd) { - for (u32 addr = selectRangeStart; addr < selectRangeEnd; addr += 4) { - Memory::Write_U32(0, addr); - } - - if (currentMIPS) { - currentMIPS->InvalidateICache(selectRangeStart, selectRangeEnd - selectRangeStart); - } -} - void ImDisasmView::PopupMenu() { bool renameFunctionPopup = false; if (ImGui::BeginPopup("context")) { @@ -812,7 +799,12 @@ void ImDisasmView::PopupMenu() { assembleOpcode(curAddress_, ""); } if (ImGui::MenuItem("NOP instructions (destructive)")) { - NopInstructions(selectRangeStart_, selectRangeEnd_); + for (u32 addr = selectRangeStart_; addr < selectRangeEnd_; addr += 4) { + Memory::Write_U32(0, addr); + } + if (currentMIPS) { + currentMIPS->InvalidateICache(selectRangeStart_, selectRangeEnd_ - selectRangeStart_); + } } ImGui::Separator(); if (ImGui::MenuItem("Rename function")) { diff --git a/UI/ImDebugger/ImDisasmView.h b/UI/ImDebugger/ImDisasmView.h index 3cbd51572054..4364fa923062 100644 --- a/UI/ImDebugger/ImDisasmView.h +++ b/UI/ImDebugger/ImDisasmView.h @@ -28,6 +28,7 @@ class ImDisasmView { void Draw(ImDrawList *drawList); void PopupMenu(); + void NotifyStep(); void ScrollRelative(int amount); @@ -131,7 +132,6 @@ class ImDisasmView { void updateStatusBarText(); void drawBranchLine(ImDrawList *list, Bounds rc, std::map &addressPositions, const BranchLine &line); void CopyInstructions(u32 startAddr, u32 endAddr, CopyInstructionsMode mode); - void NopInstructions(u32 startAddr, u32 endAddr); std::set getSelectedLineArguments(); void drawArguments(ImDrawList *list, Bounds rc, const DisassemblyLineInfo &line, float x, float y, ImColor textColor, const std::set ¤tArguments); diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index a6a450b58848..c96a38caca85 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -14,6 +14,7 @@ #include "GPU/Debugger/State.h" #include "GPU/Debugger/GECommandTable.h" #include "GPU/Debugger/Breakpoints.h" +#include "GPU/Debugger/Stepping.h" #include "GPU/Debugger/Debugger.h" #include "GPU/GPUState.h" @@ -82,6 +83,12 @@ void DrawDebugStatsWindow(ImConfig &cfg) { ImGui::End(); } +void ImGeDisasmView::NotifyStep() { + if (followPC_) { + gotoPC_ = true; + } +} + void ImGeDisasmView::Draw(GPUDebugInterface *gpuDebug) { const u32 branchColor = 0xFFA0FFFF; const u32 gteColor = 0xFFFFEFA0; @@ -123,7 +130,7 @@ void ImGeDisasmView::Draw(GPUDebugInterface *gpuDebug) { } if (pc != 0xFFFFFFFF) { - if (gotoPC_ || followPC_) { + if (gotoPC_) { selectedAddr_ = pc; gotoPC_ = false; } diff --git a/UI/ImDebugger/ImGe.h b/UI/ImDebugger/ImGe.h index a001977cf424..5e382e22c4ee 100644 --- a/UI/ImDebugger/ImGe.h +++ b/UI/ImDebugger/ImGe.h @@ -28,6 +28,8 @@ class ImGeDisasmView { selectedAddr_ = addr; } + void NotifyStep(); + private: u32 selectedAddr_ = INVALID_ADDR; u32 dragAddr_ = INVALID_ADDR; diff --git a/UI/ImDebugger/ImMemView.cpp b/UI/ImDebugger/ImMemView.cpp index ff767343c919..47c342e6e20c 100644 --- a/UI/ImDebugger/ImMemView.cpp +++ b/UI/ImDebugger/ImMemView.cpp @@ -201,6 +201,39 @@ void ImMemView::Draw(ImDrawList *drawList) { } } + ImGuiIO& io = ImGui::GetIO(); + ImVec2 mousePos = ImVec2(io.MousePos.x - canvas_p0.x, io.MousePos.y - canvas_p0.y); + if (is_hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { + // INFO_LOG(Log::System, "Mousedown %f,%f active:%d hover:%d", mousePos.x, mousePos.y, is_active, is_hovered); + onMouseDown(mousePos.x, mousePos.y, 1); + } + if (is_hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { + // INFO_LOG(Log::CPU, "Mousedown %f,%f active:%d hover:%d", mousePos.x, mousePos.y, is_active, is_hovered); + onMouseDown(mousePos.x, mousePos.y, 2); + } + if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { + // INFO_LOG(Log::System, "Mouseup %f,%f active:%d hover:%d", mousePos.x, mousePos.y, is_active, is_hovered); + if (is_hovered) { + onMouseUp(mousePos.x, mousePos.y, 1); + } + } + if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { + // INFO_LOG(Log::System, "Mousedrag %f,%f active:%d hover:%d", mousePos.x, mousePos.y, is_active, is_hovered); + if (is_hovered) { + onMouseMove(mousePos.x, mousePos.y, 1); + } + } + + if (is_hovered) { + if (io.MouseWheel > 0.0f) { // TODO: Scale steps by the value. + windowStart_ -= 4; + } else if (io.MouseWheel < 0.0f) { + windowStart_ += 4; + } + } + + ProcessKeyboardShortcuts(ImGui::IsItemFocused()); + ImGui_PopFont(); drawList->PopClipRect(); } From 39ffe92e0a6e5d9d776fbaf2223ccb1f739d8641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 12 Dec 2024 12:58:59 +0100 Subject: [PATCH 17/58] LR->RA rename, fixes --- Core/Debugger/DebugInterface.h | 2 +- Core/Debugger/DisassemblyManager.h | 2 +- Core/HLE/KernelThreadDebugInterface.h | 2 +- Core/MIPS/MIPSDebugInterface.h | 2 +- UI/ImDebugger/ImDebugger.cpp | 18 +++++++++++------- UI/ImDebugger/ImDebugger.h | 2 +- UI/ImDebugger/ImDisasmView.cpp | 4 ++-- UI/ImDebugger/ImDisasmView.h | 6 +++--- Windows/Debugger/Debugger_Disasm.cpp | 8 ++++---- Windows/ppsspp.rc | 2 +- Windows/resource.h | 2 +- 11 files changed, 27 insertions(+), 23 deletions(-) diff --git a/Core/Debugger/DebugInterface.h b/Core/Debugger/DebugInterface.h index 0c82696654b7..2ae217441252 100644 --- a/Core/Debugger/DebugInterface.h +++ b/Core/Debugger/DebugInterface.h @@ -57,7 +57,7 @@ class DebugInterface { virtual u32 GetPC() = 0; virtual void SetPC(u32 _pc) = 0; - virtual u32 GetLR() = 0; + virtual u32 GetRA() = 0; virtual void DisAsm(u32 pc, char *out, size_t outSize) = 0; diff --git a/Core/Debugger/DisassemblyManager.h b/Core/Debugger/DisassemblyManager.h index c0f990d8531c..291f1bdd4e5e 100644 --- a/Core/Debugger/DisassemblyManager.h +++ b/Core/Debugger/DisassemblyManager.h @@ -200,7 +200,7 @@ class DisassemblyManager void clear(); - void setCpu(DebugInterface* _cpu) { cpu = _cpu; }; + static void setCpu(DebugInterface* _cpu) { cpu = _cpu; }; void setMaxParamChars(int num) { maxParamChars = num; clear(); }; void getLine(u32 address, bool insertSymbols, DisassemblyLineInfo &dest, DebugInterface *cpuDebug = nullptr); void analyze(u32 address, u32 size); diff --git a/Core/HLE/KernelThreadDebugInterface.h b/Core/HLE/KernelThreadDebugInterface.h index eb3c754675ac..b6372f83a382 100644 --- a/Core/HLE/KernelThreadDebugInterface.h +++ b/Core/HLE/KernelThreadDebugInterface.h @@ -28,7 +28,7 @@ class KernelThreadDebugInterface : public MIPSDebugInterface { u32 GetGPR32Value(int reg) override { return ctx.r[reg]; } u32 GetPC() override { return ctx.pc; } - u32 GetLR() override { return ctx.r[MIPS_REG_RA]; } + u32 GetRA() override { return ctx.r[MIPS_REG_RA]; } void SetPC(u32 _pc) override { ctx.pc = _pc; } void PrintRegValue(int cat, int index, char *out, size_t outSize) override { diff --git a/Core/MIPS/MIPSDebugInterface.h b/Core/MIPS/MIPSDebugInterface.h index ac62511af03a..7899f22436cd 100644 --- a/Core/MIPS/MIPSDebugInterface.h +++ b/Core/MIPS/MIPSDebugInterface.h @@ -50,7 +50,7 @@ class MIPSDebugInterface : public DebugInterface void SetGPR32Value(int reg, u32 value) override { cpu->r[reg] = value; } u32 GetPC() override { return cpu->pc; } - u32 GetLR() override { return cpu->r[MIPS_REG_RA]; } + u32 GetRA() override { return cpu->r[MIPS_REG_RA]; } u32 GetFPCond() override { return cpu->fpcond; } void DisAsm(u32 pc, char *out, size_t outSize) override; void SetPC(u32 _pc) override { cpu->pc = _pc; } diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 000acb35c268..be2e22d8f8a9 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -880,6 +880,10 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu return; } + // TODO: Pass mipsDebug in where needed instead. + DisassemblyManager::setCpu(mipsDebug); + disasm_.View().setDebugger(mipsDebug); + // Watch the step counters to figure out when to update things. if (lastCpuStepCount_ != Core_GetSteppingCounter()) { @@ -970,7 +974,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu for (int i = 0; i < 4; i++) { char title[64]; snprintf(title, sizeof(title), "Memory %d", i + 1); - ImGui::MenuItem("Memory", nullptr, &cfg_.memViewOpen[i]); + ImGui::MenuItem(title, nullptr, &cfg_.memViewOpen[i]); } ImGui::EndMenu(); } @@ -1096,7 +1100,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu } for (int i = 0; i < 4; i++) { - mem_[i].Draw(mipsDebug, &cfg_.memViewOpen[i], i); + mem_[i].Draw(mipsDebug, cfg_, control, i); } // Process UI commands @@ -1114,11 +1118,11 @@ void ImDebugger::Snapshot() { } -void ImMemWindow::Draw(MIPSDebugInterface *mipsDebug, bool *open, int index) { +void ImMemWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl &control, int index) { char title[256]; snprintf(title, sizeof(title), "Memory %d", index); ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); - if (!ImGui::Begin(title, open, ImGuiWindowFlags_NoNavInputs)) { + if (!ImGui::Begin(title, &cfg.memViewOpen[index])) { ImGui::End(); return; } @@ -1225,11 +1229,11 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, CoreStat ImGui::SameLine(); if (ImGui::SmallButton("Goto PC")) { - disasmView_.gotoPC(); + disasmView_.GotoPC(); } ImGui::SameLine(); - if (ImGui::SmallButton("Goto LR")) { - disasmView_.gotoLR(); + if (ImGui::SmallButton("Goto RA")) { + disasmView_.GotoRA(); } if (ImGui::BeginPopup("disSearch")) { diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h index 880c4d95095b..4d6d0759f5ab 100644 --- a/UI/ImDebugger/ImDebugger.h +++ b/UI/ImDebugger/ImDebugger.h @@ -62,7 +62,7 @@ class ImDisasmWindow { // Corresponds to the CMemView dialog class ImMemWindow { public: - void Draw(MIPSDebugInterface *mipsDebug, bool *open, int index); + void Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl &control, int index); ImMemView &View() { return memView_; } diff --git a/UI/ImDebugger/ImDisasmView.cpp b/UI/ImDebugger/ImDisasmView.cpp index 0391d15cb53a..58b619c95125 100644 --- a/UI/ImDebugger/ImDisasmView.cpp +++ b/UI/ImDebugger/ImDisasmView.cpp @@ -472,7 +472,7 @@ void ImDisasmView::Draw(ImDrawList *drawList) { void ImDisasmView::NotifyStep() { if (followPC_) { - gotoPC(); + GotoPC(); } } @@ -618,7 +618,7 @@ void ImDisasmView::ProcessKeyboardShortcuts(bool focused) { } if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) { if (jumpStack_.empty()) { - gotoPC(); + GotoPC(); } else { u32 addr = jumpStack_[jumpStack_.size() - 1]; jumpStack_.pop_back(); diff --git a/UI/ImDebugger/ImDisasmView.h b/UI/ImDebugger/ImDisasmView.h index 4364fa923062..6aea42f3d298 100644 --- a/UI/ImDebugger/ImDisasmView.h +++ b/UI/ImDebugger/ImDisasmView.h @@ -71,11 +71,11 @@ class ImDisasmView { setCurAddress(newAddress); ScanVisibleFunctions(); } - void gotoPC() { + void GotoPC() { gotoAddr(debugger_->GetPC()); } - void gotoLR() { - gotoAddr(debugger_->GetLR()); + void GotoRA() { + gotoAddr(debugger_->GetRA()); } u32 getSelection() { return curAddress_; diff --git a/Windows/Debugger/Debugger_Disasm.cpp b/Windows/Debugger/Debugger_Disasm.cpp index 2711c990b1a2..dc03d9ab3540 100644 --- a/Windows/Debugger/Debugger_Disasm.cpp +++ b/Windows/Debugger/Debugger_Disasm.cpp @@ -444,9 +444,9 @@ BOOL CDisasm::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { UpdateDialog(); } break; - case IDC_GOTOLR: + case IDC_GOTORA: { - ptr->gotoAddr(cpu->GetLR()); + ptr->gotoAddr(cpu->GetRA()); SetFocus(GetDlgItem(m_hDlg, IDC_DISASMVIEW)); } break; @@ -696,7 +696,7 @@ void CDisasm::SetDebugMode(bool _bDebug, bool switchPC) EnableWindow(GetDlgItem(hDlg, IDC_STEPHLE), TRUE); EnableWindow(GetDlgItem(hDlg, IDC_STEPOUT), TRUE); EnableWindow(GetDlgItem(hDlg, IDC_GOTOPC), TRUE); - EnableWindow(GetDlgItem(hDlg, IDC_GOTOLR), TRUE); + EnableWindow(GetDlgItem(hDlg, IDC_GOTORA), TRUE); CtrlDisAsmView *ptr = DisAsmView(); ptr->setDontRedraw(false); if (switchPC) @@ -715,7 +715,7 @@ void CDisasm::SetDebugMode(bool _bDebug, bool switchPC) EnableWindow(GetDlgItem(hDlg, IDC_STEPHLE), FALSE); EnableWindow(GetDlgItem(hDlg, IDC_STEPOUT), FALSE); EnableWindow(GetDlgItem(hDlg, IDC_GOTOPC), FALSE); - EnableWindow(GetDlgItem(hDlg, IDC_GOTOLR), FALSE); + EnableWindow(GetDlgItem(hDlg, IDC_GOTORA), FALSE); CtrlRegisterList *reglist = CtrlRegisterList::getFrom(GetDlgItem(m_hDlg,IDC_REGLIST)); reglist->redraw(); } diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index c146f1226132..4f48db5f4608 100644 --- a/Windows/ppsspp.rc +++ b/Windows/ppsspp.rc @@ -180,7 +180,7 @@ BEGIN EDITTEXT IDC_THREADNAME,370,3,150,10,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER EDITTEXT IDC_ADDRESS,11,24,91,13,ES_AUTOHSCROLL | ES_WANTRETURN PUSHBUTTON "PC",IDC_GOTOPC,11,40,15,13 - PUSHBUTTON "RA",IDC_GOTOLR,28,40,14,13 + PUSHBUTTON "RA",IDC_GOTORA,28,40,14,13 COMBOBOX IDC_GOTOINT,43,40,60,76,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP CONTROL "Custom1",IDC_DISASMVIEW,"CtrlDisAsmView",WS_BORDER | WS_TABSTOP,111,16,397,304 GROUPBOX "Go to",IDC_STATIC,5,12,102,47 diff --git a/Windows/resource.h b/Windows/resource.h index d7fce4a41639..4eb9bd1419f2 100644 --- a/Windows/resource.h +++ b/Windows/resource.h @@ -70,7 +70,7 @@ #define IDC_STEP 1009 #define IDC_VERSION 1010 #define IDC_MEMVIEW 1069 -#define IDC_GOTOLR 1070 +#define IDC_GOTORA 1070 #define IDC_GOTOINT 1071 #define IDC_MEMSORT 1073 #define IDC_SYMBOLS 1097 From 58c091598b9f5a24068d054ba79446f558ca1c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 12 Dec 2024 12:59:07 +0100 Subject: [PATCH 18/58] Fix sending of random stack data along with chat messages --- Core/HLE/proAdhoc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index d4b283dabbda..81028d08245d 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -1327,7 +1327,7 @@ void timeoutFriendsRecursive(SceNetAdhocctlPeerInfo * node, int32_t* count) { } void sendChat(const std::string &chatString) { - SceNetAdhocctlChatPacketC2S chat; + SceNetAdhocctlChatPacketC2S chat{}; chat.base.opcode = OPCODE_CHAT; //TODO check network inited, check send success or not, chatlog.pushback error on failed send, pushback error on not connected if (friendFinderRunning) { From c85266359f3750442a19065b9af10633a99940a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 12 Dec 2024 15:02:44 +0100 Subject: [PATCH 19/58] More memory view work --- UI/ImDebugger/ImDebugger.cpp | 106 +++++++++++++++++++++------- UI/ImDebugger/ImDebugger.h | 20 ++++-- UI/ImDebugger/ImDisasmView.cpp | 15 ++-- UI/ImDebugger/ImDisasmView.h | 11 +-- UI/ImDebugger/ImGe.cpp | 2 +- UI/ImDebugger/ImGe.h | 3 + UI/ImDebugger/ImMemView.cpp | 119 ++++++++++++++++---------------- UI/ImDebugger/ImMemView.h | 16 +++-- ext/imgui/imgui_impl_thin3d.cpp | 4 ++ ext/imgui/imgui_impl_thin3d.h | 4 ++ 10 files changed, 191 insertions(+), 109 deletions(-) diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index be2e22d8f8a9..bc877e7b5f17 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -42,6 +42,28 @@ extern bool g_TakeScreenshot; +void ShowInMemoryViewerMenuItem(uint32_t addr, ImControl &control) { + if (ImGui::BeginMenu("Show in memory viewer")) { + for (int i = 0; i < 4; i++) { + if (ImGui::MenuItem(ImMemWindow::Title(i))) { + control.command = { ImCmd::SHOW_IN_MEMORY_VIEWER, addr, (u32)i }; + } + } + ImGui::EndMenu(); + } +} + +void ShowInWindowMenuItems(uint32_t addr, ImControl &control) { + // Enable when we implement the memory viewer + ShowInMemoryViewerMenuItem(addr, control); + if (ImGui::MenuItem("Show in CPU debugger")) { + control.command = { ImCmd::SHOW_IN_CPU_DISASM, addr }; + } + if (ImGui::MenuItem("Show in GE debugger")) { + control.command = { ImCmd::SHOW_IN_GE_DISASM, addr }; + } +} + // TODO: Style it. // Left click performs the preferred action, if any. Right click opens a menu for more. void ImClickableAddress(uint32_t addr, ImControl &control, ImCmd cmd) { @@ -57,18 +79,7 @@ void ImClickableAddress(uint32_t addr, ImControl &control, ImCmd cmd) { System_CopyStringToClipboard(temp); } ImGui::Separator(); - // Enable when we implement the memory viewer - if (cmd != ImCmd::SHOW_IN_MEMORY_VIEWER) { - if (ImGui::MenuItem("Show in memory viewer 1")) { - control.command = { ImCmd::SHOW_IN_MEMORY_VIEWER, addr }; - } - } - if (ImGui::MenuItem("Show in CPU debugger")) { - control.command = { ImCmd::SHOW_IN_CPU_DISASM, addr }; - } - if (ImGui::MenuItem("Show in GE debugger")) { - control.command = { ImCmd::SHOW_IN_GE_DISASM, addr }; - } + ShowInWindowMenuItems(addr, control); ImGui::EndPopup(); } } @@ -883,6 +894,9 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu // TODO: Pass mipsDebug in where needed instead. DisassemblyManager::setCpu(mipsDebug); disasm_.View().setDebugger(mipsDebug); + for (int i = 0; i < 4; i++) { + mem_[i].View().setDebugger(mipsDebug); + } // Watch the step counters to figure out when to update things. @@ -1024,7 +1038,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu } if (cfg_.disasmOpen) { - disasm_.Draw(mipsDebug, cfg_, coreState); + disasm_.Draw(mipsDebug, cfg_, control, coreState); } if (cfg_.regsOpen) { @@ -1100,17 +1114,32 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu } for (int i = 0; i < 4; i++) { - mem_[i].Draw(mipsDebug, cfg_, control, i); + if (cfg_.memViewOpen[i]) { + mem_[i].Draw(mipsDebug, cfg_, control, i); + } } // Process UI commands switch (control.command.cmd) { case ImCmd::SHOW_IN_CPU_DISASM: disasm_.View().gotoAddr(control.command.param); + cfg_.disasmOpen = true; + ImGui::SetWindowFocus(disasm_.Title()); break; case ImCmd::SHOW_IN_GE_DISASM: geDebugger_.View().GotoAddr(control.command.param); + cfg_.geDebuggerOpen = true; + ImGui::SetWindowFocus(geDebugger_.Title()); break; + case ImCmd::SHOW_IN_MEMORY_VIEWER: + { + u32 index = control.command.param2; + _dbg_assert_(index < 4); + mem_[index].GotoAddr(control.command.param); + cfg_.memViewOpen[index] = true; + ImGui::SetWindowFocus(ImMemWindow::Title(index)); + break; + } } } @@ -1119,28 +1148,55 @@ void ImDebugger::Snapshot() { } void ImMemWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl &control, int index) { - char title[256]; - snprintf(title, sizeof(title), "Memory %d", index); ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); - if (!ImGui::Begin(title, &cfg.memViewOpen[index])) { + if (!ImGui::Begin(Title(index), &cfg.memViewOpen[index])) { ImGui::End(); return; } - memView_.setDebugger(mipsDebug); - memView_.Draw(ImGui::GetWindowDrawList()); + // Toolbars + + if (ImGui::InputScalar("Go to addr: ", ImGuiDataType_U32, &gotoAddr_, NULL, NULL, "%08X", ImGuiInputTextFlags_EnterReturnsTrue)) { + memView_.gotoAddr(gotoAddr_); + } + // Main views - list of interesting addresses to the left, memory view to the right. + if (ImGui::BeginChild("addr_list", ImVec2(200.0f, 0.0))) { + if (ImGui::Selectable("Scratch")) { + GotoAddr(0x00010000); + } + if (ImGui::Selectable("Kernel RAM")) { + GotoAddr(0x08000000); + } + if (ImGui::Selectable("User RAM")) { + GotoAddr(0x08800000); + } + if (ImGui::Selectable("VRAM")) { + GotoAddr(0x08800000); + } + ImGui::EndChild(); + } + ImGui::SameLine(); + ImGui::BeginGroup(); + if (ImGui::BeginChild("memview", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()))) { + memView_.Draw(ImGui::GetWindowDrawList()); + ImGui::EndChild(); + } + ImGui::TextUnformatted(memView_.StatusMessage().c_str()); + ImGui::EndGroup(); ImGui::End(); } -void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, CoreState coreState) { - char title[256]; - snprintf(title, sizeof(title), "%s - Disassembly", "Allegrex MIPS"); - +const char *ImMemWindow::Title(int index) { + static const char *const titles[4] = { "Memory 1", "Memory 2", "Memory 3", "Memory 4" }; + return titles[index]; +} + +void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl &control, CoreState coreState) { disasmView_.setDebugger(mipsDebug); ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); - if (!ImGui::Begin(title, &cfg.disasmOpen, ImGuiWindowFlags_NoNavInputs)) { + if (!ImGui::Begin(Title(), &cfg.disasmOpen, ImGuiWindowFlags_NoNavInputs)) { ImGui::End(); return; } @@ -1322,7 +1378,7 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, CoreStat } ImGui::TableSetColumnIndex(1); - disasmView_.Draw(ImGui::GetWindowDrawList()); + disasmView_.Draw(ImGui::GetWindowDrawList(), control); ImGui::EndTable(); ImGui::TextUnformatted(disasmView_.StatusBarText().c_str()); diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h index 4d6d0759f5ab..1af95f0169f4 100644 --- a/UI/ImDebugger/ImDebugger.h +++ b/UI/ImDebugger/ImDebugger.h @@ -32,13 +32,16 @@ struct ImConfig; // Corresponds to the CDisasm dialog class ImDisasmWindow { public: - void Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, CoreState coreState); + void Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl &control, CoreState coreState); ImDisasmView &View() { return disasmView_; } void DirtySymbolMap() { symsDirty_ = true; } + const char *Title() const { + return "CPU Debugger"; + } private: // We just keep the state directly in the window. Can refactor later. @@ -47,7 +50,7 @@ class ImDisasmWindow { INVALID_ADDR = 0xFFFFFFFF, }; - u32 gotoAddr_ = 0x1000; + u32 gotoAddr_ = 0x08800000; // Symbol cache std::vector symCache_; @@ -69,6 +72,10 @@ class ImMemWindow { void DirtySymbolMap() { symsDirty_ = true; } + void GotoAddr(u32 addr) { + memView_.gotoAddr(addr); + } + static const char *Title(int index); private: // We just keep the state directly in the window. Can refactor later. @@ -84,6 +91,8 @@ class ImMemWindow { ImMemView memView_; char searchTerm_[64]{}; + + u32 gotoAddr_ = 0x08800000; }; struct ImConfig { @@ -137,12 +146,13 @@ enum class ImCmd { TRIGGER_FIND_POPUP, SHOW_IN_CPU_DISASM, SHOW_IN_GE_DISASM, - SHOW_IN_MEMORY_VIEWER, + SHOW_IN_MEMORY_VIEWER, // param is address, param2 is viewer index }; struct ImCommand { ImCmd cmd; uint32_t param; + uint32_t param2; }; struct ImControl { @@ -178,5 +188,7 @@ class ImDebugger { ImConfig cfg_{}; }; -// Simple custom controls +// Simple custom controls and utilities. void ImClickableAddress(uint32_t addr, ImControl &control, ImCmd cmd); +void ShowInWindowMenuItems(uint32_t addr, ImControl &control); +void ShowInMemoryViewerMenuItem(uint32_t addr, ImControl &control); diff --git a/UI/ImDebugger/ImDisasmView.cpp b/UI/ImDebugger/ImDisasmView.cpp index 58b619c95125..e0d53513f74b 100644 --- a/UI/ImDebugger/ImDisasmView.cpp +++ b/UI/ImDebugger/ImDisasmView.cpp @@ -17,6 +17,7 @@ #include "Core/Core.h" #include "Core/CoreParameter.h" #include "UI/ImDebugger/ImDisasmView.h" +#include "UI/ImDebugger/ImDebugger.h" ImDisasmView::ImDisasmView() { curAddress_ = 0; @@ -295,7 +296,7 @@ void ImDisasmView::drawArguments(ImDrawList *drawList, Bounds rc, const Disassem } } -void ImDisasmView::Draw(ImDrawList *drawList) { +void ImDisasmView::Draw(ImDrawList *drawList, ImControl &control) { if (!debugger_->isAlive()) { return; } @@ -465,7 +466,7 @@ void ImDisasmView::Draw(ImDrawList *drawList) { ImGui_PopFont(); ImGui::OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight); - PopupMenu(); + PopupMenu(control); drawList->PopClipRect(); } @@ -760,22 +761,20 @@ void ImDisasmView::CopyInstructions(u32 startAddr, u32 endAddr, CopyInstructions } } -void ImDisasmView::PopupMenu() { +void ImDisasmView::PopupMenu(ImControl &control) { bool renameFunctionPopup = false; if (ImGui::BeginPopup("context")) { ImGui::Text("Address: %08x", curAddress_); if (ImGui::MenuItem("Toggle breakpoint", "F9")) { toggleBreakpoint(); } - if (ImGui::MenuItem("Go to in memory view")) { - // SendMessage(GetParent(wnd), WM_DEB_GOTOHEXEDIT, curAddress, 0); + ShowInMemoryViewerMenuItem(curAddress_, control); + if (ImGui::MenuItem("Copy address")) { + CopyInstructions(selectRangeStart_, selectRangeEnd_, CopyInstructionsMode::ADDRESSES); } if (ImGui::MenuItem("Copy instruction (disasm)")) { CopyInstructions(selectRangeStart_, selectRangeEnd_, CopyInstructionsMode::DISASM); } - if (ImGui::MenuItem("Copy address")) { - CopyInstructions(selectRangeStart_, selectRangeEnd_, CopyInstructionsMode::ADDRESSES); - } if (ImGui::MenuItem("Copy instruction (hex)")) { CopyInstructions(selectRangeStart_, selectRangeEnd_, CopyInstructionsMode::OPCODES); } diff --git a/UI/ImDebugger/ImDisasmView.h b/UI/ImDebugger/ImDisasmView.h index 6aea42f3d298..34893822f6f2 100644 --- a/UI/ImDebugger/ImDisasmView.h +++ b/UI/ImDebugger/ImDisasmView.h @@ -14,6 +14,7 @@ #include "Core/Debugger/DebugInterface.h" struct ImConfig; +struct ImControl; // Corresponds to CtrlDisAsmView // TODO: Fold out common code. @@ -25,9 +26,9 @@ class ImDisasmView { // Public variables bounds to imgui checkboxes bool followPC_ = true; - void Draw(ImDrawList *drawList); + void Draw(ImDrawList *drawList, ImControl &control); - void PopupMenu(); + void PopupMenu(ImControl &control); void NotifyStep(); void ScrollRelative(int amount); @@ -157,7 +158,7 @@ class ImDisasmView { int opcodeStart; int argumentsStart; int arrowsStart; - } pixelPositions_; + } pixelPositions_{}; std::vector jumpStack_; @@ -171,6 +172,6 @@ class ImDisasmView { int lastSteppingCount_ = 0; std::string statusBarText_; - u32 funcBegin_; - char funcNameTemp_[128]; + u32 funcBegin_ = 0; + char funcNameTemp_[128]{}; }; diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index c96a38caca85..52fae575671c 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -245,7 +245,7 @@ void ImGeDisasmView::Draw(GPUDebugInterface *gpuDebug) { void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterface *gpuDebug) { ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("GE Debugger", &cfg.geDebuggerOpen)) { + if (!ImGui::Begin(Title(), &cfg.geDebuggerOpen)) { ImGui::End(); return; } diff --git a/UI/ImDebugger/ImGe.h b/UI/ImDebugger/ImGe.h index 5e382e22c4ee..f4cadbda7548 100644 --- a/UI/ImDebugger/ImGe.h +++ b/UI/ImDebugger/ImGe.h @@ -54,6 +54,9 @@ class ImGeDebuggerWindow { ImGeDisasmView &View() { return disasmView_; } + const char *Title() const { + return "GE Debugger"; + } private: ImGeDisasmView disasmView_; diff --git a/UI/ImDebugger/ImMemView.cpp b/UI/ImDebugger/ImMemView.cpp index 47c342e6e20c..b3eb3e58243a 100644 --- a/UI/ImDebugger/ImMemView.cpp +++ b/UI/ImDebugger/ImMemView.cpp @@ -19,19 +19,10 @@ #include "UI/ImDebugger/ImMemView.h" ImMemView::ImMemView() { - const float fontScale = 1.0f / g_display.dpi_scale_real_y; - charWidth_ = g_Config.iFontWidth * fontScale; - rowHeight_ = g_Config.iFontHeight * fontScale; - offsetPositionY_ = offsetLine * rowHeight_; - windowStart_ = curAddress_; selectRangeStart_ = curAddress_; selectRangeEnd_ = curAddress_ + 1; lastSelectReset_ = curAddress_; - - addressStartX_ = charWidth_; - hexStartX_ = addressStartX_ + 9 * charWidth_; - asciiStartX_ = hexStartX_ + (rowSize_ * 3 + 1) * charWidth_; } ImMemView::~ImMemView() {} @@ -47,6 +38,12 @@ LRESULT CALLBACK ImMemView::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM l } */ +static uint32_t pickTagColor(std::string_view tag) { + uint32_t colors[6] = { 0xFF301010, 0xFF103030, 0xFF403010, 0xFF103000, 0xFF301030, 0xFF101030 }; + int which = XXH3_64bits(tag.data(), tag.length()) % ARRAY_SIZE(colors); + return colors[which]; +} + void ImMemView::Draw(ImDrawList *drawList) { if (!debugger_->isAlive()) { return; @@ -56,6 +53,13 @@ void ImMemView::Draw(ImDrawList *drawList) { rowHeight_ = ImGui::GetTextLineHeightWithSpacing(); charWidth_ = ImGui::CalcTextSize("W", nullptr, false, -1.0f).x; + charHeight_ = ImGui::GetTextLineHeight(); + + offsetPositionY_ = offsetLine * rowHeight_; + + addressStartX_ = charWidth_; + hexStartX_ = addressStartX_ + 9 * charWidth_; + asciiStartX_ = hexStartX_ + (rowSize_ * 3 + 1) * charWidth_; const ImVec2 canvas_p0 = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates! const ImVec2 canvas_sz = ImGui::GetContentRegionAvail(); // Resize canvas to what's available @@ -104,8 +108,7 @@ void ImMemView::Draw(ImDrawList *drawList) { char temp[32]; uint32_t address = windowStart_ + i * rowSize_; snprintf(temp, sizeof(temp), "%08X", address); - - drawList->AddText(ImVec2(canvas_p0.x + addressStartX_, canvas_p0.y + rowY), IM_COL32(0, 0, 0x60, 0xFF), temp); + drawList->AddText(ImVec2(canvas_p0.x + addressStartX_, canvas_p0.y + rowY), IM_COL32(0xE0, 0xE0, 0xE0, 0xFF), temp); union { uint32_t words[4]; @@ -118,11 +121,11 @@ void ImMemView::Draw(ImDrawList *drawList) { for (int j = 0; j < rowSize_; j++) { const uint32_t byteAddress = (address + j) & ~0xC0000000; - std::string tag; + std::string_view tag; bool tagContinues = false; - for (auto info : memRangeInfo) { + for (const auto &info : memRangeInfo) { if (info.start <= byteAddress && info.start + info.size > byteAddress) { - tag = info.tag; + tag = info.tag; // doesn't take ownership! tagContinues = byteAddress + 1 < info.start + info.size; } } @@ -143,60 +146,58 @@ void ImMemView::Draw(ImDrawList *drawList) { } ImColor standardBG = 0xFF000000; - ImColor continueBGCol = 0xFF000000; - ImColor hexBGCol = 0xFF000000; + bool continueRect = false; + + ImColor hexBGCol = 0; ImColor hexTextCol = 0xFFFFFFFF; - ImColor asciiBGCol = 0xFF000000; + ImColor asciiBGCol = 0; ImColor asciiTextCol = 0xFFFFFFFF; int underline = -1; + const ImColor primarySelFg = 0xFFFFFFFF; + const ImColor primarySelBg = 0xFFFF9933; + const ImColor secondarySelFg = 0xFFFFFFFF; + const ImColor secondarySelBg = 0xFF808080; + if (address + j >= selectRangeStart_ && address + j < selectRangeEnd_ && !searching_) { if (asciiSelected_) { - hexBGCol = ImColor(0xFFC0C0C0); - hexTextCol = 0x000000; - asciiBGCol = hasFocus_ ? 0xFF9933 : 0xC0C0C0; - asciiTextCol = hasFocus_ ? 0xFFFFFF : 0x000000; + hexBGCol = secondarySelBg; + hexTextCol = secondarySelFg; + asciiBGCol = primarySelBg; + asciiTextCol = primarySelFg; } else { - hexBGCol = hasFocus_ ? 0xFF9933 : 0xC0C0C0; - hexTextCol = hasFocus_ ? 0xFFFFFF : 0x000000; - asciiBGCol = 0xC0C0C0; - asciiTextCol = 0x000000; + hexBGCol = primarySelBg; + hexTextCol = primarySelFg; + asciiBGCol = secondarySelBg; + asciiTextCol = secondarySelFg; if (address + j == curAddress_) underline = selectedNibble_; } if (!tag.empty() && tagContinues) { - continueBGCol = pickTagColor(tag); + continueRect = true; } } else if (!tag.empty()) { hexBGCol = pickTagColor(tag); - continueBGCol = hexBGCol; - asciiBGCol = pickTagColor(tag); - hexLen = tagContinues ? 3 : 2; + continueRect = tagContinues; + asciiBGCol = hexBGCol; } ImColor fg = hexTextCol; ImColor bg = hexBGCol; + // SelectObject(hdc, underline == 0 ? (HGDIOBJ)underlineFont : (HGDIOBJ)font); + if (bg != 0) { + int bgWidth = 2; // continueRect ? 3 : 2; + drawList->AddRectFilled(ImVec2(canvas_p0.x + hexX - 1, canvas_p0.y + rowY), ImVec2(canvas_p0.x + hexX + charWidth_ * bgWidth, canvas_p0.y + rowY + charHeight_), bg); + drawList->AddText(ImVec2(canvas_p0.x + hexX, canvas_p0.y + rowY), fg, &temp[0], &temp[2]); + } if (underline >= 0) { - // SelectObject(hdc, underline == 0 ? (HGDIOBJ)underlineFont : (HGDIOBJ)font); - drawList->AddText(ImVec2(canvas_p0.x + hexX, canvas_p0.y + rowY), fg, &temp[0], &temp[1]); - // SelectObject(hdc, underline == 0 ? (HGDIOBJ)font : (HGDIOBJ)underlineFont); - drawList->AddText(ImVec2(canvas_p0.x + hexX + charWidth_, canvas_p0.y + rowY), fg, &temp[1]); - - // If the tag keeps going, draw the BG too. - if (continueBGCol != standardBG) { - // setTextColors(0x000000, continueBGCol); - // TextOutA(hdc, hexX + charWidth_ * 2, rowY, &temp[2], 1); - } - } else { - if (continueBGCol != hexBGCol) { - drawList->AddText(ImVec2(canvas_p0.x + hexX, canvas_p0.y + rowY), fg, &temp[0], &temp[2]); - } else { - drawList->AddText(ImVec2(canvas_p0.x + hexX, canvas_p0.y + rowY), fg, &temp[0], &temp[2]); - } + float x = canvas_p0.x + hexX + underline * charWidth_; + drawList->AddRectFilled(ImVec2(x, canvas_p0.y + rowY + charHeight_ - 2), ImVec2(x + charWidth_, canvas_p0.y + rowY + charHeight_), IM_COL32(0xFF, 0xFF, 0xFF, 0xFF)); } - // setTextColors(asciiTextCol, asciiBGCol); - // TextOutA(hdc, asciiX, rowY, &c, 1); + fg = asciiTextCol; + bg = asciiBGCol; + drawList->AddRectFilled(ImVec2(canvas_p0.x + asciiX, canvas_p0.y + rowY), ImVec2(canvas_p0.x + asciiX + charWidth_, canvas_p0.y + rowY + charHeight_), bg); drawList->AddText(ImVec2(canvas_p0.x + asciiX, canvas_p0.y + rowY), fg, &c, &c + 1); } } @@ -226,15 +227,19 @@ void ImMemView::Draw(ImDrawList *drawList) { if (is_hovered) { if (io.MouseWheel > 0.0f) { // TODO: Scale steps by the value. - windowStart_ -= 4; + windowStart_ -= rowSize_ * 4; } else if (io.MouseWheel < 0.0f) { - windowStart_ += 4; + windowStart_ += rowSize_ * 4; } } ProcessKeyboardShortcuts(ImGui::IsItemFocused()); ImGui_PopFont(); + + ImGui::OpenPopupOnItemClick("memcontext", ImGuiPopupFlags_MouseButtonRight); + PopupMenu(); + drawList->PopClipRect(); } @@ -372,19 +377,18 @@ void ImMemView::onMouseDown(float x, float y, int button) { } void ImMemView::PopupMenu() { - if (ImGui::BeginPopup("context")) { + if (ImGui::BeginPopup("memcontext")) { int32_t selectedSize = selectRangeEnd_ - selectRangeStart_; bool enable16 = !asciiSelected_ && (selectedSize == 1 || (selectedSize & 1) == 0); bool enable32 = !asciiSelected_ && (selectedSize == 1 || (selectedSize & 3) == 0); - + /* if (ImGui::MenuItem("Dump memory")) { - /* DumpMemoryWindow dump(wnd, debugger_); dump.exec(); break; - */ } + */ if (ImGui::MenuItem("Copy value (8-bit)")) { size_t tempSize = 3 * selectedSize + 1; @@ -603,7 +607,8 @@ void ImMemView::GotoPoint(int x, int y, GotoMode mode) { switch (col % 3) { case 0: targetNibble = 0; break; case 1: targetNibble = 1; break; - case 2: return; // don't change position when clicking on the space + // case 2: return; // don't change position when clicking on the space + case 2: targetNibble = 0; break; // TODO: split the difference? Anyway, this feels better. } targetAscii = false; @@ -849,9 +854,3 @@ void ImMemView::setHighlightType(MemBlockFlags flags) { updateStatusBarText(); } } - -uint32_t ImMemView::pickTagColor(const std::string &tag) { - int colors[6] = { 0xe0FFFF, 0xFFE0E0, 0xE8E8FF, 0xFFE0FF, 0xE0FFE0, 0xFFFFE0 }; - int which = XXH3_64bits(tag.c_str(), tag.length()) % ARRAY_SIZE(colors); - return colors[which]; -} diff --git a/UI/ImDebugger/ImMemView.h b/UI/ImDebugger/ImMemView.h index 1b22a44d21e3..22a0420d6694 100644 --- a/UI/ImDebugger/ImMemView.h +++ b/UI/ImDebugger/ImMemView.h @@ -43,13 +43,16 @@ class ImMemView { void toggleOffsetScale(CommonToggles toggle); void setHighlightType(MemBlockFlags flags); + const std::string &StatusMessage() const { + return statusMessage_; + } + private: void ProcessKeyboardShortcuts(bool focused); bool ParseSearchString(const std::string &query, bool asHex, std::vector &data); void updateStatusBarText(); void search(bool continueSearch); - uint32_t pickTagColor(const std::string &tag); enum class GotoMode { RESET, @@ -57,6 +60,7 @@ class ImMemView { FROM_CUR, EXTEND, }; + static GotoMode GotoModeFromModifiers(bool isRightClick); void UpdateSelectRange(uint32_t target, GotoMode mode); void GotoPoint(int x, int y, GotoMode mode); @@ -67,28 +71,28 @@ class ImMemView { static wchar_t szClassName[]; DebugInterface *debugger_ = nullptr; - // Whether to draw things using focused styles. - bool hasFocus_ = false; MemBlockFlags highlightFlags_ = MemBlockFlags::ALLOC; // Current cursor position. - uint32_t curAddress_ = 0; + uint32_t curAddress_ = 0x08800000; // Selected range, which should always be around the cursor. uint32_t selectRangeStart_ = 0; uint32_t selectRangeEnd_ = 0; // Last select reset position, for selecting ranges. uint32_t lastSelectReset_ = 0; // Address of the first displayed byte. - uint32_t windowStart_ = 0; + uint32_t windowStart_ = 0x08800000; // Number of bytes displayed per row. int rowSize_ = 16; // Width of one monospace character (to maintain grid.) int charWidth_ = 0; + // Height of one monospace character (to maintain grid.) + int charHeight_ = 0; // Height of one row of bytes. int rowHeight_ = 0; // Y position of offset header (at top.) - int offsetPositionY_; + int offsetPositionY_ = 0; // X position of addresses (at left.) int addressStartX_ = 0; // X position of hex display. diff --git a/ext/imgui/imgui_impl_thin3d.cpp b/ext/imgui/imgui_impl_thin3d.cpp index 430c59fd6deb..ab7b561546ba 100644 --- a/ext/imgui/imgui_impl_thin3d.cpp +++ b/ext/imgui/imgui_impl_thin3d.cpp @@ -313,6 +313,10 @@ void ImGui_PopFont() { ImGui::PopFont(); } +ImFont *ImGui_GetFixedFont() { + return g_fixedFont; +} + void ImGui_ImplThin3d_Shutdown() { BackendData* bd = ImGui_ImplThin3d_GetBackendData(); IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); diff --git a/ext/imgui/imgui_impl_thin3d.h b/ext/imgui/imgui_impl_thin3d.h index 39d40b8f4f18..04edae30ca6d 100644 --- a/ext/imgui/imgui_impl_thin3d.h +++ b/ext/imgui/imgui_impl_thin3d.h @@ -55,6 +55,10 @@ IMGUI_IMPL_API ImTextureID ImGui_ImplThin3d_AddFBAsTextureTemp(Draw::Framebuffer void ImGui_PushFixedFont(); void ImGui_PopFont(); +// To get metrics etc. +ImFont *ImGui_GetFixedFont(); + + // Helper structure to hold the data needed by one rendering context into one OS window // (Used by example's main.cpp. Used by multi-viewport features. Probably NOT used by your own engine/app.) struct ImGui_ImplThin3dH_Window { From 3844f751b3e93cbcbdf59df9730862a1a60a2ef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 12 Dec 2024 17:47:22 +0100 Subject: [PATCH 20/58] Warning fix --- UI/ImDebugger/ImDebugger.cpp | 5 +++++ UI/ImDebugger/ImDisasmView.h | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index bc877e7b5f17..435d761137b9 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -1140,6 +1140,11 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu ImGui::SetWindowFocus(ImMemWindow::Title(index)); break; } + case ImCmd::TRIGGER_FIND_POPUP: + // TODO + break; + case ImCmd::NONE: + break; } } diff --git a/UI/ImDebugger/ImDisasmView.h b/UI/ImDebugger/ImDisasmView.h index 34893822f6f2..70c81c5320f7 100644 --- a/UI/ImDebugger/ImDisasmView.h +++ b/UI/ImDebugger/ImDisasmView.h @@ -169,7 +169,6 @@ class ImDisasmView { bool mapReloaded_ = false; int positionLocked_ = 0; - int lastSteppingCount_ = 0; std::string statusBarText_; u32 funcBegin_ = 0; From a858032e460204cf4d9b62892cedb4873625b940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 12 Dec 2024 17:57:43 +0100 Subject: [PATCH 21/58] Remove obsolete accounting for time spent stepping the GE (we no longer block) --- Core/HLE/HLE.cpp | 10 ++-------- Core/HLE/HLE.h | 2 -- GPU/Common/GPUDebugInterface.h | 4 ---- GPU/Debugger/Stepping.cpp | 7 +++++-- GPU/GPUCommon.cpp | 29 +++++------------------------ GPU/GPUCommon.h | 7 ------- 6 files changed, 12 insertions(+), 47 deletions(-) diff --git a/Core/HLE/HLE.cpp b/Core/HLE/HLE.cpp index 2e0a3710a3f9..a6809cc592d3 100644 --- a/Core/HLE/HLE.cpp +++ b/Core/HLE/HLE.cpp @@ -758,16 +758,11 @@ void *GetQuickSyscallFunc(MIPSOpcode op) { return (void *)&CallSyscallWithoutFlags; } -void hleSetSteppingTime(double t) { - hleSteppingTime += t; -} - void hleSetFlipTime(double t) { hleFlipTime = t; } -void CallSyscall(MIPSOpcode op) -{ +void CallSyscall(MIPSOpcode op) { PROFILE_THIS_SCOPE("syscall"); double start = 0.0; // need to initialize to fix the race condition where coreCollectDebugStats is enabled in the middle of this func. if (coreCollectDebugStats) { @@ -797,11 +792,10 @@ void CallSyscall(MIPSOpcode op) u32 callno = (op >> 6) & 0xFFFFF; //20 bits int funcnum = callno & 0xFFF; int modulenum = (callno & 0xFF000) >> 12; - double total = time_now_d() - start - hleSteppingTime; + double total = time_now_d() - start; if (total >= hleFlipTime) total -= hleFlipTime; _dbg_assert_msg_(total >= 0.0, "Time spent in syscall became negative"); - hleSteppingTime = 0.0; hleFlipTime = 0.0; updateSyscallStats(modulenum, funcnum, total); } diff --git a/Core/HLE/HLE.h b/Core/HLE/HLE.h index eff5fba1c90a..b4cf7f6874ed 100644 --- a/Core/HLE/HLE.h +++ b/Core/HLE/HLE.h @@ -114,8 +114,6 @@ void hleRunInterrupts(); void hleDebugBreak(); // Don't set temp regs to 0xDEADBEEF. void hleSkipDeadbeef(); -// Set time spent in debugger (for more useful debug stats while debugging.) -void hleSetSteppingTime(double t); // Set time spent in realtime sync. void hleSetFlipTime(double t); // Check if the current syscall context is kernel. diff --git a/GPU/Common/GPUDebugInterface.h b/GPU/Common/GPUDebugInterface.h index b68288b0ee15..35e607db75a6 100644 --- a/GPU/Common/GPUDebugInterface.h +++ b/GPU/Common/GPUDebugInterface.h @@ -213,10 +213,6 @@ class GPUDebugInterface { virtual GPUDebugOp DisassembleOp(u32 pc, u32 op) = 0; virtual std::vector DisassembleOpRange(u32 startpc, u32 endpc) = 0; - // Enter/exit stepping mode. Mainly for better debug stats on time taken. - virtual void NotifySteppingEnter() = 0; - virtual void NotifySteppingExit() = 0; - virtual u32 GetRelativeAddress(u32 data) = 0; virtual u32 GetVertexAddress() = 0; virtual u32 GetIndexAddress() = 0; diff --git a/GPU/Debugger/Stepping.cpp b/GPU/Debugger/Stepping.cpp index 122cde8714f3..b4a0a81d1093 100644 --- a/GPU/Debugger/Stepping.cpp +++ b/GPU/Debugger/Stepping.cpp @@ -21,6 +21,7 @@ #include "Common/Log.h" #include "Common/Thread/ThreadUtil.h" #include "Core/Core.h" +#include "Core/HW/Display.h" #include "GPU/Common/GPUDebugInterface.h" #include "GPU/Debugger/Stepping.h" #include "GPU/GPUState.h" @@ -44,6 +45,10 @@ static bool isStepping; // Number of times we've entered stepping, to detect a resume asynchronously. static int stepCounter = 0; +// Debug stats. +static double g_timeSteppingStarted; +static double g_timeSpentStepping; + static std::mutex pauseLock; static PauseAction pauseAction = PAUSE_CONTINUE; static std::mutex actionLock; @@ -162,13 +167,11 @@ static void StartStepping() { // Play it safe so we don't keep resetting. lastGState.cmdmem[1] |= 0x01000000; } - gpuDebug->NotifySteppingEnter(); isStepping = true; stepCounter++; } static void StopStepping() { - gpuDebug->NotifySteppingExit(); lastGState = gstate; isStepping = false; } diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 4c296feb71e1..c9ff5defe397 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -44,6 +44,7 @@ #include "GPU/Common/TextureCacheCommon.h" #include "GPU/Debugger/Debugger.h" #include "GPU/Debugger/Record.h" +#include "GPU/Debugger/Stepping.h" void GPUCommon::Flush() { drawEngineCommon_->DispatchFlush(); @@ -105,7 +106,6 @@ void GPUCommon::Reinitialize() { isbreak = false; drawCompleteTicks = 0; busyTicks = 0; - timeSpentStepping_ = 0.0; interruptsEnabled_ = true; if (textureCache_) @@ -615,23 +615,6 @@ u32 GPUCommon::Break(int mode) { return currentList->id; } -void GPUCommon::NotifySteppingEnter() { - if (coreCollectDebugStats) { - timeSteppingStarted_ = time_now_d(); - } -} -void GPUCommon::NotifySteppingExit() { - if (coreCollectDebugStats) { - if (timeSteppingStarted_ <= 0.0) { - ERROR_LOG(Log::G3D, "Mismatched stepping enter/exit."); - } - double total = time_now_d() - timeSteppingStarted_; - _dbg_assert_msg_(total >= 0.0, "Time spent stepping became negative"); - timeSpentStepping_ += total; - timeSteppingStarted_ = 0.0; - } -} - bool GPUCommon::InterpretList(DisplayList &list) { // Initialized to avoid a race condition with bShowDebugStats changing. double start = 0.0; @@ -683,6 +666,9 @@ bool GPUCommon::InterpretList(DisplayList &list) { FinishDeferred(); _dbg_assert_(!GPURecord::IsActive()); gpuState = GPUSTATE_BREAK; + if (coreCollectDebugStats) { + gpuStats.msProcessingDisplayLists += time_now_d() - start; + } return false; } } @@ -706,12 +692,7 @@ bool GPUCommon::InterpretList(DisplayList &list) { list.offsetAddr = gstate_c.offsetAddr; if (coreCollectDebugStats) { - double total = time_now_d() - start - timeSpentStepping_; - _dbg_assert_msg_(total >= 0.0, "Time spent DL processing became negative"); - hleSetSteppingTime(timeSpentStepping_); - DisplayNotifySleep(timeSpentStepping_); - timeSpentStepping_ = 0.0; - gpuStats.msProcessingDisplayLists += total; + gpuStats.msProcessingDisplayLists += time_now_d() - start; } return gpuState == GPUSTATE_DONE || gpuState == GPUSTATE_ERROR; } diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 89465eca70a6..ec7bb50deb0e 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -349,9 +349,6 @@ class GPUCommon : public GPUDebugInterface { GPUDebugOp DisassembleOp(u32 pc, u32 op) override; std::vector DisassembleOpRange(u32 startpc, u32 endpc) override; - void NotifySteppingEnter() override; - void NotifySteppingExit() override; - u32 GetRelativeAddress(u32 data) override; u32 GetVertexAddress() override; u32 GetIndexAddress() override; @@ -510,8 +507,4 @@ class GPUCommon : public GPUDebugInterface { void PopDLQueue(); void CheckDrawSync(); int GetNextListIndex(); - - // Debug stats. - double timeSteppingStarted_; - double timeSpentStepping_; }; From 20a17a0e8df04c21c28b5195bf21941ce3549237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 12 Dec 2024 18:53:42 +0100 Subject: [PATCH 22/58] Reorganize DebugInterface etc a bit. KernelThreadDebugInterface no longer has a useless copy of a MIPSDebugInterface. --- Core/Debugger/Breakpoints.cpp | 8 +-- Core/Debugger/Breakpoints.h | 8 +-- Core/Debugger/DebugInterface.h | 34 +--------- Core/Debugger/DisassemblyManager.cpp | 36 +++++----- Core/Debugger/DisassemblyManager.h | 16 +++-- .../WebSocket/BreakpointSubscriber.cpp | 4 +- Core/Debugger/WebSocket/CPUCoreSubscriber.cpp | 12 ++-- .../Debugger/WebSocket/SteppingSubscriber.cpp | 2 +- Core/HLE/KernelThreadDebugInterface.h | 6 +- Core/HLE/sceKernelThread.cpp | 2 +- Core/MIPS/MIPSAnalyst.cpp | 3 +- Core/MIPS/MIPSAnalyst.h | 12 ++-- Core/MIPS/MIPSDebugInterface.cpp | 66 +++++++------------ Core/MIPS/MIPSDebugInterface.h | 47 +++++++------ UI/ImDebugger/ImDisasmView.h | 9 +-- UI/ImDebugger/ImMemView.h | 6 +- UI/ImDebugger/ImStructViewer.cpp | 8 +-- Windows/Debugger/BreakpointWindow.cpp | 10 +-- Windows/Debugger/BreakpointWindow.h | 8 +-- Windows/Debugger/CtrlDisAsmView.h | 6 +- Windows/Debugger/CtrlMemView.h | 7 +- Windows/Debugger/CtrlRegisterList.h | 12 ++-- Windows/Debugger/DebuggerShared.cpp | 6 +- Windows/Debugger/Debugger_Disasm.cpp | 4 +- Windows/Debugger/Debugger_Disasm.h | 4 +- Windows/Debugger/Debugger_Lists.cpp | 8 +-- Windows/Debugger/Debugger_Lists.h | 4 +- Windows/Debugger/Debugger_MemoryDlg.cpp | 4 +- Windows/Debugger/Debugger_MemoryDlg.h | 5 +- Windows/Debugger/Debugger_VFPUDlg.cpp | 2 +- Windows/Debugger/DumpMemoryWindow.cpp | 9 +-- Windows/Debugger/EditSymbolsWindow.cpp | 9 +-- Windows/Debugger/WatchItemWindow.cpp | 2 +- 33 files changed, 168 insertions(+), 211 deletions(-) diff --git a/Core/Debugger/Breakpoints.cpp b/Core/Debugger/Breakpoints.cpp index d26d5e7f2f5a..b8ae35013ca4 100644 --- a/Core/Debugger/Breakpoints.cpp +++ b/Core/Debugger/Breakpoints.cpp @@ -647,12 +647,12 @@ void BreakpointManager::Update(u32 addr) { System_Notify(SystemNotification::DISASSEMBLY); } -bool BreakpointManager::ValidateLogFormat(DebugInterface *cpu, const std::string &fmt) { +bool BreakpointManager::ValidateLogFormat(MIPSDebugInterface *cpu, const std::string &fmt) { std::string ignore; return EvaluateLogFormat(cpu, fmt, ignore); } -bool BreakpointManager::EvaluateLogFormat(DebugInterface *cpu, const std::string &fmt, std::string &result) { +bool BreakpointManager::EvaluateLogFormat(MIPSDebugInterface *cpu, const std::string &fmt, std::string &result) { PostfixExpression exp; result.clear(); @@ -697,7 +697,7 @@ bool BreakpointManager::EvaluateLogFormat(DebugInterface *cpu, const std::string } } - if (!cpu->initExpression(expression.c_str(), exp)) { + if (!initExpression(cpu, expression.c_str(), exp)) { return false; } @@ -707,7 +707,7 @@ bool BreakpointManager::EvaluateLogFormat(DebugInterface *cpu, const std::string float f; } expResult; char resultString[256]; - if (!cpu->parseExpression(exp, expResult.u)) { + if (!parseExpression(cpu, exp, expResult.u)) { return false; } diff --git a/Core/Debugger/Breakpoints.h b/Core/Debugger/Breakpoints.h index fb99be271a86..e8ed006a43c6 100644 --- a/Core/Debugger/Breakpoints.h +++ b/Core/Debugger/Breakpoints.h @@ -21,7 +21,7 @@ #include #include -#include "Core/Debugger/DebugInterface.h" +#include "Core/MIPS/MIPSDebugInterface.h" enum BreakAction : u32 { BREAK_ACTION_IGNORE = 0x00, @@ -45,7 +45,7 @@ struct BreakPointCond { u32 Evaluate() { u32 result; - if (debug->parseExpression(expression, result) == false) + if (parseExpression(debug, expression, result) == false) return 0; return result; } @@ -185,8 +185,8 @@ class BreakpointManager { void Update(u32 addr = 0); - bool ValidateLogFormat(DebugInterface *cpu, const std::string &fmt); - bool EvaluateLogFormat(DebugInterface *cpu, const std::string &fmt, std::string &result); + bool ValidateLogFormat(MIPSDebugInterface *cpu, const std::string &fmt); + bool EvaluateLogFormat(MIPSDebugInterface *cpu, const std::string &fmt, std::string &result); private: size_t FindBreakpoint(u32 addr, bool matchTemp = false, bool temp = false); diff --git a/Core/Debugger/DebugInterface.h b/Core/Debugger/DebugInterface.h index 2ae217441252..11559e20b7f8 100644 --- a/Core/Debugger/DebugInterface.h +++ b/Core/Debugger/DebugInterface.h @@ -26,49 +26,21 @@ struct MemMap; class DebugInterface { public: - virtual int getInstructionSize(int instruction) = 0; - - virtual bool isAlive() = 0; - virtual bool isBreakpoint(unsigned int address) = 0; - virtual void setBreakpoint(unsigned int address) = 0; - virtual void clearBreakpoint(unsigned int address) = 0; - virtual void clearAllBreakpoints() = 0; - virtual void toggleBreakpoint(unsigned int address) = 0; - virtual unsigned int readMemory(unsigned int address) {return 0;} - virtual void step() {} - virtual void runToBreakpoint() {} - virtual int getColor(unsigned int address, bool darkMode) const {return darkMode ? 0xFF101010 : 0xFFFFFFFF;} - virtual std::string getDescription(unsigned int address) {return "";} - virtual bool initExpression(const char* exp, PostfixExpression& dest) { return false; }; - virtual bool parseExpression(PostfixExpression& exp, u32& dest) { return false; }; - virtual u32 GetHi() = 0; virtual u32 GetLo() = 0; virtual u32 GetLLBit() = 0; virtual u32 GetFPCond() = 0; - virtual void SetHi(u32 val) { }; - virtual void SetLo(u32 val) { }; - virtual const char *GetName() = 0; + virtual void SetHi(u32 val) = 0; + virtual void SetLo(u32 val) = 0; virtual u32 GetGPR32Value(int reg) = 0; - virtual void SetGPR32Value(int reg, u32 value) = 0; - virtual float GetFPR32Value(int reg) { return -1.0f; } - virtual float GetVPR32Value(int reg) { return -1.0f; } virtual u32 GetPC() = 0; virtual void SetPC(u32 _pc) = 0; virtual u32 GetRA() = 0; - virtual void DisAsm(u32 pc, char *out, size_t outSize) = 0; - // More stuff for debugger - virtual int GetNumCategories() = 0; - virtual int GetNumRegsInCategory(int cat) = 0; - virtual const char *GetCategoryName(int cat) = 0; - virtual std::string GetRegName(int cat, int index) = 0; - virtual void PrintRegValue(int cat, int index, char *out, size_t outSize) { - snprintf(out, outSize, "%08X", GetGPR32Value(index)); - } virtual u32 GetRegValue(int cat, int index) = 0; virtual void SetRegValue(int cat, int index, u32 value) {} + virtual void PrintRegValue(int cat, int index, char *out, size_t outSize) = 0; }; diff --git a/Core/Debugger/DisassemblyManager.cpp b/Core/Debugger/DisassemblyManager.cpp index b497532da1aa..a7cc6c4abfd6 100644 --- a/Core/Debugger/DisassemblyManager.cpp +++ b/Core/Debugger/DisassemblyManager.cpp @@ -25,9 +25,11 @@ #include "Common/CommonTypes.h" #include "Common/Data/Encoding/Utf8.h" +#include "Common/Log.h" #include "Common/StringUtils.h" #include "Core/MemMap.h" #include "Core/System.h" +#include "Core/MIPS/MIPSDebugInterface.h" #include "Core/MIPS/MIPSCodeUtils.h" #include "Core/MIPS/MIPSTables.h" #include "Core/Debugger/DebugInterface.h" @@ -36,7 +38,7 @@ std::map DisassemblyManager::entries; std::recursive_mutex DisassemblyManager::entriesLock_; -DebugInterface* DisassemblyManager::cpu; +DebugInterface* DisassemblyManager::cpu_; int DisassemblyManager::maxParamChars = 29; bool isInInterval(u32 start, u32 size, u32 value) { @@ -517,10 +519,10 @@ void DisassemblyFunction::generateBranchLines() u32 end = address+size; std::lock_guard guard(lock_); - DebugInterface* cpu = DisassemblyManager::getCpu(); + DebugInterface *cpu = DisassemblyManager::getCpu(); for (u32 funcPos = address; funcPos < end; funcPos += 4) { - MIPSAnalyst::MipsOpcodeInfo opInfo = MIPSAnalyst::GetOpcodeInfo(cpu,funcPos); + MIPSAnalyst::MipsOpcodeInfo opInfo = MIPSAnalyst::GetOpcodeInfo(cpu, funcPos); bool inFunction = (opInfo.branchTarget >= address && opInfo.branchTarget < end); if (opInfo.isBranch && !opInfo.isBranchToRegister && !opInfo.isLinkedBranch && inFunction) { @@ -610,7 +612,7 @@ void DisassemblyFunction::load() } } - DebugInterface* cpu = DisassemblyManager::getCpu(); + DebugInterface *cpu = DisassemblyManager::getCpu(); u32 funcPos = address; u32 funcEnd = address+size; @@ -752,7 +754,7 @@ bool DisassemblyOpcode::disassemble(u32 address, DisassemblyLineInfo &dest, bool char opcode[64],arguments[256]; char dizz[512]; - cpuDebug->DisAsm(address, dizz, sizeof(dizz)); + DisAsm(address, dizz, sizeof(dizz)); parseDisasm(dizz, opcode, sizeof(opcode), arguments, sizeof(arguments), insertSymbols); dest.type = DISTYPE_OPCODE; dest.name = opcode; @@ -809,7 +811,7 @@ void DisassemblyMacro::setMacroLi(u32 _immediate, u8 _rt) numOpcodes = 2; } -void DisassemblyMacro::setMacroMemory(const std::string &_name, u32 _immediate, u8 _rt, int _dataSize) +void DisassemblyMacro::setMacroMemory(std::string_view _name, u32 _immediate, u8 _rt, int _dataSize) { type = MACRO_MEMORYIMM; name = _name; @@ -836,9 +838,9 @@ bool DisassemblyMacro::disassemble(u32 address, DisassemblyLineInfo &dest, bool addressSymbol = g_symbolMap->GetLabelString(immediate); if (!addressSymbol.empty() && insertSymbols) { - snprintf(buffer, sizeof(buffer), "%s,%s", cpuDebug->GetRegName(0, rt).c_str(), addressSymbol.c_str()); + snprintf(buffer, sizeof(buffer), "%s,%s", MIPSDebugInterface::GetRegName(0, rt).c_str(), addressSymbol.c_str()); } else { - snprintf(buffer, sizeof(buffer), "%s,0x%08X", cpuDebug->GetRegName(0, rt).c_str(), immediate); + snprintf(buffer, sizeof(buffer), "%s,0x%08X", MIPSDebugInterface::GetRegName(0, rt).c_str(), immediate); } dest.params = buffer; @@ -851,9 +853,9 @@ bool DisassemblyMacro::disassemble(u32 address, DisassemblyLineInfo &dest, bool addressSymbol = g_symbolMap->GetLabelString(immediate); if (!addressSymbol.empty() && insertSymbols) { - snprintf(buffer, sizeof(buffer), "%s,%s", cpuDebug->GetRegName(0, rt).c_str(), addressSymbol.c_str()); + snprintf(buffer, sizeof(buffer), "%s,%s", MIPSDebugInterface::GetRegName(0, rt).c_str(), addressSymbol.c_str()); } else { - snprintf(buffer, sizeof(buffer), "%s,0x%08X", cpuDebug->GetRegName(0, rt).c_str(), immediate); + snprintf(buffer, sizeof(buffer), "%s,0x%08X", MIPSDebugInterface::GetRegName(0, rt).c_str(), immediate); } dest.params = buffer; @@ -876,9 +878,7 @@ bool DisassemblyMacro::disassemble(u32 address, DisassemblyLineInfo &dest, bool DisassemblyData::DisassemblyData(u32 _address, u32 _size, DataType _type): address(_address), size(_size), type(_type) { - if (!PSP_IsInited()) - return; - + _dbg_assert_(PSP_IsInited()); hash = computeHash(address,size); createLines(); } @@ -1088,14 +1088,10 @@ void DisassemblyData::createLines() } -DisassemblyComment::DisassemblyComment(u32 _address, u32 _size, std::string _name, std::string _param) - : address(_address), size(_size), name(_name), param(_param) -{ - -} +DisassemblyComment::DisassemblyComment(u32 _address, u32 _size, std::string_view _name, std::string_view _param) + : address(_address), size(_size), name(_name), param(_param) {} -bool DisassemblyComment::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug) -{ +bool DisassemblyComment::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug) { dest.type = DISTYPE_OTHER; dest.name = name; dest.params = param; diff --git a/Core/Debugger/DisassemblyManager.h b/Core/Debugger/DisassemblyManager.h index 291f1bdd4e5e..04c53049f73c 100644 --- a/Core/Debugger/DisassemblyManager.h +++ b/Core/Debugger/DisassemblyManager.h @@ -17,6 +17,9 @@ #pragma once +#include +#include + #include "ppsspp_config.h" #include #include "Common/CommonTypes.h" @@ -120,7 +123,7 @@ class DisassemblyMacro: public DisassemblyEntry DisassemblyMacro(u32 _address): address(_address) { } void setMacroLi(u32 _immediate, u8 _rt); - void setMacroMemory(const std::string &_name, u32 _immediate, u8 _rt, int _dataSize); + void setMacroMemory(std::string_view _name, u32 _immediate, u8 _rt, int _dataSize); void recheck() override { }; int getNumLines() override { return 1; }; @@ -175,7 +178,7 @@ class DisassemblyData: public DisassemblyEntry class DisassemblyComment: public DisassemblyEntry { public: - DisassemblyComment(u32 _address, u32 _size, std::string name, std::string param); + DisassemblyComment(u32 _address, u32 _size, std::string_view name, std::string_view param); void recheck() override { }; int getNumLines() override { return 1; }; @@ -193,14 +196,13 @@ class DisassemblyComment: public DisassemblyEntry class DebugInterface; -class DisassemblyManager -{ +class DisassemblyManager { public: ~DisassemblyManager(); void clear(); - static void setCpu(DebugInterface* _cpu) { cpu = _cpu; }; + static void setCpu(DebugInterface *cpu) { cpu_ = cpu; }; void setMaxParamChars(int num) { maxParamChars = num; clear(); }; void getLine(u32 address, bool insertSymbols, DisassemblyLineInfo &dest, DebugInterface *cpuDebug = nullptr); void analyze(u32 address, u32 size); @@ -210,12 +212,12 @@ class DisassemblyManager u32 getNthPreviousAddress(u32 address, int n = 1); u32 getNthNextAddress(u32 address, int n = 1); - static DebugInterface* getCpu() { return cpu; }; + static DebugInterface *getCpu() { return cpu_; }; static int getMaxParamChars() { return maxParamChars; }; private: static std::map entries; static std::recursive_mutex entriesLock_; - static DebugInterface* cpu; + static DebugInterface *cpu_; static int maxParamChars; }; diff --git a/Core/Debugger/WebSocket/BreakpointSubscriber.cpp b/Core/Debugger/WebSocket/BreakpointSubscriber.cpp index 1c0c039f577c..2f3b788b3473 100644 --- a/Core/Debugger/WebSocket/BreakpointSubscriber.cpp +++ b/Core/Debugger/WebSocket/BreakpointSubscriber.cpp @@ -74,7 +74,7 @@ struct WebSocketCPUBreakpointParams { if (hasCondition) { if (!req.ParamString("condition", &condition)) return false; - if (!currentDebugMIPS->initExpression(condition.c_str(), compiledCondition)) { + if (!initExpression(currentDebugMIPS, condition.c_str(), compiledCondition)) { req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError())); return false; } @@ -292,7 +292,7 @@ struct WebSocketMemoryBreakpointParams { if (hasCondition) { if (!req.ParamString("condition", &condition)) return false; - if (!currentDebugMIPS->initExpression(condition.c_str(), compiledCondition)) { + if (!initExpression(currentDebugMIPS, condition.c_str(), compiledCondition)) { req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError())); return false; } diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp index 60c46c4f28ee..26ba747b3169 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp @@ -134,16 +134,16 @@ void WebSocketCPUGetAllRegs(DebuggerRequest &req) { JsonWriter &json = req.Respond(); json.pushArray("categories"); - for (int c = 0; c < cpuDebug->GetNumCategories(); ++c) { + for (int c = 0; c < MIPSDebugInterface::GetNumCategories(); ++c) { json.pushDict(); json.writeInt("id", c); - json.writeString("name", cpuDebug->GetCategoryName(c)); + json.writeString("name", MIPSDebugInterface::GetCategoryName(c)); - int total = cpuDebug->GetNumRegsInCategory(c); + int total = MIPSDebugInterface::GetNumRegsInCategory(c); json.pushArray("registerNames"); for (int r = 0; r < total; ++r) - json.writeString(cpuDebug->GetRegName(c, r)); + json.writeString(MIPSDebugInterface::GetRegName(c, r)); if (c == 0) { json.writeString("pc"); json.writeString("hi"); @@ -402,10 +402,10 @@ void WebSocketCPUEvaluate(DebuggerRequest &req) { u32 val; PostfixExpression postfix; - if (!cpuDebug->initExpression(exp.c_str(), postfix)) { + if (!initExpression(cpuDebug, exp.c_str(), postfix)) { return req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError())); } - if (!cpuDebug->parseExpression(postfix, val)) { + if (!parseExpression(cpuDebug, postfix, val)) { return req.Fail(StringFromFormat("Could not evaluate expression: %s", getExpressionError())); } diff --git a/Core/Debugger/WebSocket/SteppingSubscriber.cpp b/Core/Debugger/WebSocket/SteppingSubscriber.cpp index 019a4868e4fc..4f7662bada8e 100644 --- a/Core/Debugger/WebSocket/SteppingSubscriber.cpp +++ b/Core/Debugger/WebSocket/SteppingSubscriber.cpp @@ -288,6 +288,6 @@ void WebSocketSteppingState::AddThreadCondition(uint32_t breakpointAddress, uint BreakPointCond cond; cond.debug = currentDebugMIPS; cond.expressionString = StringFromFormat("threadid == 0x%08x", threadID); - if (currentDebugMIPS->initExpression(cond.expressionString.c_str(), cond.expression)) + if (initExpression(currentDebugMIPS, cond.expressionString.c_str(), cond.expression)) g_breakpoints.ChangeBreakPointAddCond(breakpointAddress, cond); } diff --git a/Core/HLE/KernelThreadDebugInterface.h b/Core/HLE/KernelThreadDebugInterface.h index b6372f83a382..dbab6d57e144 100644 --- a/Core/HLE/KernelThreadDebugInterface.h +++ b/Core/HLE/KernelThreadDebugInterface.h @@ -21,14 +21,16 @@ #include "Core/HLE/sceKernelThread.h" #include "Core/MIPS/MIPSDebugInterface.h" -class KernelThreadDebugInterface : public MIPSDebugInterface { +class KernelThreadDebugInterface : public DebugInterface { public: - KernelThreadDebugInterface(MIPSState *c, PSPThreadContext &t) : MIPSDebugInterface(c), ctx(t) { + KernelThreadDebugInterface(PSPThreadContext &t) : ctx(t) { } u32 GetGPR32Value(int reg) override { return ctx.r[reg]; } u32 GetPC() override { return ctx.pc; } u32 GetRA() override { return ctx.r[MIPS_REG_RA]; } + u32 GetLLBit() override { return 0; } + u32 GetFPCond() override { return ctx.fpcond; } void SetPC(u32 _pc) override { ctx.pc = _pc; } void PrintRegValue(int cat, int index, char *out, size_t outSize) override { diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index 6c517c3159b4..6652f02dc336 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -372,7 +372,7 @@ class ActionAfterCallback : public PSPAction class PSPThread : public KernelObject { public: - PSPThread() : debug(currentMIPS, context) {} + PSPThread() : debug(context) {} const char *GetName() override { return nt.name; } const char *GetTypeName() override { return GetStaticTypeName(); } diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index 6816d9172d94..a85f01a26234 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -35,6 +35,7 @@ #include "Core/System.h" #include "Core/MIPS/MIPS.h" #include "Core/MIPS/MIPSVFPUUtils.h" +#include "Core/MIPS/MIPSDebugInterface.h" #include "Core/MIPS/MIPSTables.h" #include "Core/MIPS/MIPSAnalyst.h" #include "Core/MIPS/MIPSCodeUtils.h" @@ -1424,7 +1425,7 @@ namespace MIPSAnalyst { return vec; } - MipsOpcodeInfo GetOpcodeInfo(DebugInterface* cpu, u32 address) { + MipsOpcodeInfo GetOpcodeInfo(DebugInterface *cpu, u32 address) { MipsOpcodeInfo info; memset(&info, 0, sizeof(info)); diff --git a/Core/MIPS/MIPSAnalyst.h b/Core/MIPS/MIPSAnalyst.h index a00e2e89adc4..98b227eaf7ef 100644 --- a/Core/MIPS/MIPSAnalyst.h +++ b/Core/MIPS/MIPSAnalyst.h @@ -22,12 +22,10 @@ #include "Common/CommonTypes.h" #include "Common/File/Path.h" +#include "Core/Debugger/DebugInterface.h" #include "Core/MIPS/MIPS.h" -class DebugInterface; - -namespace MIPSAnalyst -{ +namespace MIPSAnalyst { const int MIPS_NUM_GPRS = 32; struct RegisterAnalysisResults { @@ -139,8 +137,8 @@ namespace MIPSAnalyst bool IsOpMemoryWrite(u32 pc); bool OpHasDelaySlot(u32 pc); - typedef struct { - DebugInterface* cpu; + struct MipsOpcodeInfo { + DebugInterface *cpu; u32 opcodeAddress; MIPSOpcode encodedOpcode; @@ -163,7 +161,7 @@ namespace MIPSAnalyst bool hasRelevantAddress; u32 relevantAddress; - } MipsOpcodeInfo; + }; MipsOpcodeInfo GetOpcodeInfo(DebugInterface* cpu, u32 address); diff --git a/Core/MIPS/MIPSDebugInterface.cpp b/Core/MIPS/MIPSDebugInterface.cpp index 1b894ffdc5c2..650096975e4e 100644 --- a/Core/MIPS/MIPSDebugInterface.cpp +++ b/Core/MIPS/MIPSDebugInterface.cpp @@ -52,10 +52,9 @@ enum ReferenceIndexType { }; -class MipsExpressionFunctions: public IExpressionFunctions -{ +class MipsExpressionFunctions : public IExpressionFunctions { public: - MipsExpressionFunctions(DebugInterface* cpu): cpu(cpu) { } + MipsExpressionFunctions(DebugInterface *_cpu): cpu(_cpu) {} bool parseReference(char* str, uint32_t& referenceIndex) override { @@ -64,12 +63,12 @@ class MipsExpressionFunctions: public IExpressionFunctions char reg[8]; snprintf(reg, sizeof(reg), "r%d", i); - if (strcasecmp(str, reg) == 0 || strcasecmp(str, cpu->GetRegName(0, i).c_str()) == 0) + if (strcasecmp(str, reg) == 0 || strcasecmp(str, MIPSDebugInterface::GetRegName(0, i).c_str()) == 0) { referenceIndex = i; return true; } - else if (strcasecmp(str, cpu->GetRegName(1, i).c_str()) == 0) + else if (strcasecmp(str, MIPSDebugInterface::GetRegName(1, i).c_str()) == 0) { referenceIndex = REF_INDEX_FPU | i; return true; @@ -85,7 +84,7 @@ class MipsExpressionFunctions: public IExpressionFunctions for (int i = 0; i < 128; i++) { - if (strcasecmp(str, cpu->GetRegName(2, i).c_str()) == 0) + if (strcasecmp(str, MIPSDebugInterface::GetRegName(2, i).c_str()) == 0) { referenceIndex = REF_INDEX_VFPU | i; return true; @@ -200,18 +199,9 @@ class MipsExpressionFunctions: public IExpressionFunctions } private: - DebugInterface* cpu; + DebugInterface *cpu; }; - - -void MIPSDebugInterface::DisAsm(u32 pc, char *out, size_t outSize) { - if (Memory::IsValidAddress(pc)) - MIPSDisAsm(Memory::Read_Opcode_JIT(pc), pc, out, outSize); - else - truncate_cpy(out, outSize, "-"); -} - unsigned int MIPSDebugInterface::readMemory(unsigned int address) { if (Memory::IsValidRange(address, 4)) return Memory::ReadUnchecked_Instruction(address).encoding; @@ -252,7 +242,7 @@ int MIPSDebugInterface::getColor(unsigned int address, bool darkMode) const { int n = g_symbolMap->GetFunctionNum(address); if (n == -1) { - return DebugInterface::getColor(address, darkMode); + return darkMode ? 0xFF101010 : 0xFFFFFFFF; } else if (darkMode) { return colorsDark[n % ARRAY_SIZE(colorsDark)]; } else { @@ -264,28 +254,6 @@ std::string MIPSDebugInterface::getDescription(unsigned int address) { return g_symbolMap->GetDescription(address); } -bool MIPSDebugInterface::initExpression(const char* exp, PostfixExpression& dest) -{ - MipsExpressionFunctions funcs(this); - return initPostfixExpression(exp,&funcs,dest); -} - -bool MIPSDebugInterface::parseExpression(PostfixExpression& exp, u32& dest) -{ - MipsExpressionFunctions funcs(this); - return parsePostfixExpression(exp,&funcs,dest); -} - -void MIPSDebugInterface::runToBreakpoint() -{ - -} - -const char *MIPSDebugInterface::GetName() -{ - return ("R4"); -} - std::string MIPSDebugInterface::GetRegName(int cat, int index) { static const char * const regName[32] = { "zero", "at", "v0", "v1", @@ -304,9 +272,9 @@ std::string MIPSDebugInterface::GetRegName(int cat, int index) { "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31", }; - if (cat == 0 && (unsigned)index < sizeof(regName)) { + if (cat == 0 && (unsigned)index < ARRAY_SIZE(regName)) { return regName[index]; - } else if (cat == 1 && (unsigned)index < sizeof(fpRegName)) { + } else if (cat == 1 && (unsigned)index < ARRAY_SIZE(fpRegName)) { return fpRegName[index]; } else if (cat == 2) { return GetVectorNotation(index, V_Single); @@ -314,3 +282,19 @@ std::string MIPSDebugInterface::GetRegName(int cat, int index) { return "???"; } +bool initExpression(DebugInterface *debug, const char* exp, PostfixExpression& dest) { + MipsExpressionFunctions funcs(debug); + return initPostfixExpression(exp, &funcs, dest); +} + +bool parseExpression(DebugInterface *debug, PostfixExpression& exp, u32& dest) { + MipsExpressionFunctions funcs(debug); + return parsePostfixExpression(exp, &funcs, dest); +} + +void DisAsm(u32 pc, char *out, size_t outSize) { + if (Memory::IsValidAddress(pc)) + MIPSDisAsm(Memory::Read_Opcode_JIT(pc), pc, out, outSize); + else + truncate_cpy(out, outSize, "-"); +} diff --git a/Core/MIPS/MIPSDebugInterface.h b/Core/MIPS/MIPSDebugInterface.h index 7899f22436cd..d6dc40963f6e 100644 --- a/Core/MIPS/MIPSDebugInterface.h +++ b/Core/MIPS/MIPSDebugInterface.h @@ -28,43 +28,36 @@ class MIPSDebugInterface : public DebugInterface MIPSState *cpu; public: MIPSDebugInterface(MIPSState *_cpu) { cpu = _cpu; } - int getInstructionSize(int instruction) override { return 4; } - bool isAlive() override; - bool isBreakpoint(unsigned int address) override; - void setBreakpoint(unsigned int address) override; - void clearBreakpoint(unsigned int address) override; - void clearAllBreakpoints() override; - void toggleBreakpoint(unsigned int address) override; - unsigned int readMemory(unsigned int address) override; - void step() override {} - void runToBreakpoint() override; - int getColor(unsigned int address, bool darkMode) const override; - std::string getDescription(unsigned int address) override; - bool initExpression(const char* exp, PostfixExpression& dest) override; - bool parseExpression(PostfixExpression& exp, u32& dest) override; - - //overridden functions - const char *GetName() override; + int getInstructionSize(int instruction) { return 4; } + bool isAlive(); + bool isBreakpoint(unsigned int address); + void setBreakpoint(unsigned int address); + void clearBreakpoint(unsigned int address); + void clearAllBreakpoints(); + void toggleBreakpoint(unsigned int address); + unsigned int readMemory(unsigned int address); + int getColor(unsigned int address, bool darkMode) const; + std::string getDescription(unsigned int address); + u32 GetGPR32Value(int reg) override { return cpu->r[reg]; } - float GetFPR32Value(int reg) override { return cpu->f[reg]; } - void SetGPR32Value(int reg, u32 value) override { cpu->r[reg] = value; } + float GetFPR32Value(int reg) { return cpu->f[reg]; } + void SetGPR32Value(int reg, u32 value) { cpu->r[reg] = value; } u32 GetPC() override { return cpu->pc; } u32 GetRA() override { return cpu->r[MIPS_REG_RA]; } u32 GetFPCond() override { return cpu->fpcond; } - void DisAsm(u32 pc, char *out, size_t outSize) override; void SetPC(u32 _pc) override { cpu->pc = _pc; } - const char *GetCategoryName(int cat) override { + static const char *GetCategoryName(int cat) { static const char *const names[3] = { "GPR", "FPU", "VFPU" }; return names[cat]; } - int GetNumCategories() override { return 3; } - int GetNumRegsInCategory(int cat) override { - static int r[3] = { 32, 32, 128 }; + static int GetNumCategories() { return 3; } + static constexpr int GetNumRegsInCategory(int cat) { + constexpr int r[3] = { 32, 32, 128 }; return r[cat]; } - std::string GetRegName(int cat, int index) override; + static std::string GetRegName(int cat, int index); void PrintRegValue(int cat, int index, char *out, size_t outSize) override { switch (cat) { @@ -130,3 +123,7 @@ class MIPSDebugInterface : public DebugInterface } } }; + +bool initExpression(DebugInterface *debug, const char* exp, PostfixExpression& dest); +bool parseExpression(DebugInterface *debug, PostfixExpression& exp, u32& dest); +void DisAsm(u32 pc, char *out, size_t outSize); diff --git a/UI/ImDebugger/ImDisasmView.h b/UI/ImDebugger/ImDisasmView.h index 70c81c5320f7..7ebe2adf10fc 100644 --- a/UI/ImDebugger/ImDisasmView.h +++ b/UI/ImDebugger/ImDisasmView.h @@ -11,7 +11,7 @@ #include "Common/Math/geom2d.h" #include "Core/Debugger/DisassemblyManager.h" -#include "Core/Debugger/DebugInterface.h" +#include "Core/MIPS/MIPSDebugInterface.h" struct ImConfig; struct ImControl; @@ -45,14 +45,15 @@ class ImDisasmView { void getOpcodeText(u32 address, char *dest, int bufsize); u32 yToAddress(float y); - void setDebugger(DebugInterface *deb) { + void setDebugger(MIPSDebugInterface *deb) { if (debugger_ != deb) { debugger_ = deb; curAddress_ = debugger_->GetPC(); manager.setCpu(deb); } } - DebugInterface *getDebugger() { + + MIPSDebugInterface *getDebugger() { return debugger_; } @@ -147,7 +148,7 @@ class ImDisasmView { bool hasFocus_ = true; bool showHex_ = false; - DebugInterface *debugger_ = nullptr; + MIPSDebugInterface *debugger_ = nullptr; u32 windowStart_ = 0; int visibleRows_ = 1; diff --git a/UI/ImDebugger/ImMemView.h b/UI/ImDebugger/ImMemView.h index 22a0420d6694..e0235f251757 100644 --- a/UI/ImDebugger/ImMemView.h +++ b/UI/ImDebugger/ImMemView.h @@ -23,10 +23,10 @@ class ImMemView { ImMemView(); ~ImMemView(); - void setDebugger(DebugInterface *deb) { + void setDebugger(MIPSDebugInterface *deb) { debugger_ = deb; } - DebugInterface *getDebugger() { + MIPSDebugInterface *getDebugger() { return debugger_; } std::vector searchString(const std::string &searchQuery); @@ -69,7 +69,7 @@ class ImMemView { void PopupMenu(); static wchar_t szClassName[]; - DebugInterface *debugger_ = nullptr; + MIPSDebugInterface *debugger_ = nullptr; MemBlockFlags highlightFlags_ = MemBlockFlags::ALLOC; diff --git a/UI/ImDebugger/ImStructViewer.cpp b/UI/ImDebugger/ImStructViewer.cpp index 73bee1a5145a..3abb0301d87d 100644 --- a/UI/ImDebugger/ImStructViewer.cpp +++ b/UI/ImDebugger/ImStructViewer.cpp @@ -336,8 +336,8 @@ void ImStructViewer::DrawWatch() { if (!watch.expression.empty()) { u32 val; PostfixExpression postfix; - if (mipsDebug_->initExpression(watch.expression.c_str(), postfix) - && mipsDebug_->parseExpression(postfix, val)) { + if (initExpression(mipsDebug_, watch.expression.c_str(), postfix) + && parseExpression(mipsDebug_, postfix, val)) { address = val; } } else { @@ -394,8 +394,8 @@ void ImStructViewer::DrawNewWatchEntry() { PostfixExpression postfix; if (newWatch_.typePathName.empty()) { newWatch_.error = "type can't be empty"; - } else if (!mipsDebug_->initExpression(newWatch_.expression, postfix) - || !mipsDebug_->parseExpression(postfix, val)) { + } else if (!initExpression(mipsDebug_, newWatch_.expression, postfix) + || !parseExpression(mipsDebug_, postfix, val)) { newWatch_.error = "invalid expression"; } else { std::string watchName = newWatch_.name; diff --git a/Windows/Debugger/BreakpointWindow.cpp b/Windows/Debugger/BreakpointWindow.cpp index 0af371cd1806..bd7b6b15b5e0 100644 --- a/Windows/Debugger/BreakpointWindow.cpp +++ b/Windows/Debugger/BreakpointWindow.cpp @@ -135,14 +135,14 @@ bool BreakpointWindow::fetchDialogData(HWND hwnd) // parse address GetWindowTextA(GetDlgItem(hwnd, IDC_BREAKPOINT_ADDRESS), str, 256); - if (cpu->initExpression(str, exp) == false) + if (initExpression(cpu, str, exp) == false) { snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError()); MessageBoxA(hwnd, errorMessage, "Error", MB_OK); return false; } - if (cpu->parseExpression(exp, address) == false) + if (parseExpression(cpu, exp, address) == false) { snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError()); MessageBoxA(hwnd, errorMessage, "Error", MB_OK); @@ -153,14 +153,14 @@ bool BreakpointWindow::fetchDialogData(HWND hwnd) { // parse size GetWindowTextA(GetDlgItem(hwnd, IDC_BREAKPOINT_SIZE), str, 256); - if (cpu->initExpression(str, exp) == false) + if (initExpression(cpu, str, exp) == false) { snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError()); MessageBoxA(hwnd, errorMessage, "Error", MB_OK); return false; } - if (cpu->parseExpression(exp, size) == false) + if (parseExpression(cpu, exp, size) == false) { snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError()); MessageBoxA(hwnd, errorMessage, "Error", MB_OK); @@ -175,7 +175,7 @@ bool BreakpointWindow::fetchDialogData(HWND hwnd) compiledCondition.clear(); if (!condition.empty()) { - if (cpu->initExpression(condition.c_str(), compiledCondition) == false) + if (initExpression(cpu, condition.c_str(), compiledCondition) == false) { snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", condition.c_str(), getExpressionError()); MessageBoxA(hwnd, errorMessage, "Error", MB_OK); diff --git a/Windows/Debugger/BreakpointWindow.h b/Windows/Debugger/BreakpointWindow.h index 06024e7c2f43..76202ac80a19 100644 --- a/Windows/Debugger/BreakpointWindow.h +++ b/Windows/Debugger/BreakpointWindow.h @@ -3,14 +3,14 @@ #include "Common/CommonWindows.h" #include "Common/CommonTypes.h" #include "Core/Debugger/DebugInterface.h" +#include "Core/MIPS/MIPSDebugInterface.h" struct BreakPoint; struct MemCheck; -class BreakpointWindow -{ +class BreakpointWindow { HWND parentHwnd; - DebugInterface* cpu; + MIPSDebugInterface *cpu; bool memory; bool read; @@ -31,7 +31,7 @@ class BreakpointWindow INT_PTR DlgFunc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam); public: - BreakpointWindow(HWND parent, DebugInterface* cpu): cpu(cpu) + BreakpointWindow(HWND parent, MIPSDebugInterface* cpu): cpu(cpu) { parentHwnd = parent; memory = true; diff --git a/Windows/Debugger/CtrlDisAsmView.h b/Windows/Debugger/CtrlDisAsmView.h index b217f6746d39..5bb06ef651ce 100644 --- a/Windows/Debugger/CtrlDisAsmView.h +++ b/Windows/Debugger/CtrlDisAsmView.h @@ -19,7 +19,7 @@ #include "Common/CommonWindows.h" #include "Common/Log.h" -#include "Core/Debugger/DebugInterface.h" +#include "Core/MIPS/MIPSDebugInterface.h" #include "Core/Debugger/DisassemblyManager.h" class CtrlDisAsmView @@ -38,7 +38,7 @@ class CtrlDisAsmView bool hasFocus; bool showHex; - DebugInterface *debugger; + MIPSDebugInterface *debugger; static TCHAR szClassName[]; u32 windowStart; @@ -108,7 +108,7 @@ class CtrlDisAsmView u32 yToAddress(int y); void setDontRedraw(bool b) { dontRedraw = b; }; - void setDebugger(DebugInterface *deb) + void setDebugger(MIPSDebugInterface *deb) { debugger=deb; curAddress=debugger->GetPC(); diff --git a/Windows/Debugger/CtrlMemView.h b/Windows/Debugger/CtrlMemView.h index e006e531e9cc..0c29632fa51d 100644 --- a/Windows/Debugger/CtrlMemView.h +++ b/Windows/Debugger/CtrlMemView.h @@ -13,6 +13,7 @@ #include #include "Core/Debugger/DebugInterface.h" #include "Core/Debugger/MemBlockInfo.h" +#include "Core/MIPS/MIPSDebugInterface.h" enum OffsetSpacing { offsetSpace = 3, // the number of blank lines that should be left to make space for the offsets @@ -34,10 +35,10 @@ class CtrlMemView static LRESULT CALLBACK wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); static CtrlMemView *getFrom(HWND wnd); - void setDebugger(DebugInterface *deb) { + void setDebugger(MIPSDebugInterface *deb) { debugger_ = deb; } - DebugInterface *getDebugger() { + MIPSDebugInterface *getDebugger() { return debugger_; } std::vector searchString(const std::string &searchQuery); @@ -74,7 +75,7 @@ class CtrlMemView void ScrollCursor(int bytes, GotoMode mdoe); static wchar_t szClassName[]; - DebugInterface *debugger_ = nullptr; + MIPSDebugInterface *debugger_ = nullptr; HWND wnd; HFONT font; diff --git a/Windows/Debugger/CtrlRegisterList.h b/Windows/Debugger/CtrlRegisterList.h index 34afb67de02d..41fa19c91fa7 100644 --- a/Windows/Debugger/CtrlRegisterList.h +++ b/Windows/Debugger/CtrlRegisterList.h @@ -16,7 +16,7 @@ //To get a class instance to be able to access it, just use // CtrlRegisterList::getFrom(GetDlgItem(yourdialog, IDC_yourid)). -#include "../../Core/Debugger/DebugInterface.h" +#include "Core/MIPS/MIPSDebugInterface.h" class CtrlRegisterList { @@ -32,7 +32,7 @@ class CtrlRegisterList bool selecting = false; bool hasFocus = false; - DebugInterface *cpu = nullptr; + MIPSDebugInterface *cpu = nullptr; static TCHAR szClassName[]; u32 lastPC = 0; @@ -60,17 +60,15 @@ class CtrlRegisterList int yToIndex(int y); - void setCPU(DebugInterface *deb) - { + void setCPU(MIPSDebugInterface *deb) { cpu = deb; - - int regs = cpu->GetNumRegsInCategory(0); + constexpr int regs = MIPSDebugInterface::GetNumRegsInCategory(0); lastCat0Values = new u32[regs+3]; changedCat0Regs = new bool[regs+3]; memset(lastCat0Values, 0, (regs+3) * sizeof(u32)); memset(changedCat0Regs, 0, (regs+3) * sizeof(bool)); } - DebugInterface *getCPU() + MIPSDebugInterface *getCPU() { return cpu; } diff --git a/Windows/Debugger/DebuggerShared.cpp b/Windows/Debugger/DebuggerShared.cpp index 35bdfe2cb583..5e30faa94cff 100644 --- a/Windows/Debugger/DebuggerShared.cpp +++ b/Windows/Debugger/DebuggerShared.cpp @@ -1,4 +1,5 @@ #include "Common/Data/Encoding/Utf8.h" +#include "Core/MIPS/MIPSDebugInterface.h" #include "DebuggerShared.h" #include "../InputBox.h" @@ -6,8 +7,9 @@ bool parseExpression(const char* exp, DebugInterface* cpu, u32& dest) { PostfixExpression postfix; - if (cpu->initExpression(exp,postfix) == false) return false; - return cpu->parseExpression(postfix,dest); + if (initExpression(cpu, exp, postfix) == false) + return false; + return parseExpression(cpu, postfix, dest); } void displayExpressionError(HWND hwnd) diff --git a/Windows/Debugger/Debugger_Disasm.cpp b/Windows/Debugger/Debugger_Disasm.cpp index dc03d9ab3540..4ca97a53cdb8 100644 --- a/Windows/Debugger/Debugger_Disasm.cpp +++ b/Windows/Debugger/Debugger_Disasm.cpp @@ -91,12 +91,12 @@ LRESULT CALLBACK FuncListProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lPa static constexpr UINT_PTR IDT_UPDATE = 0xC0DE0042; static constexpr UINT UPDATE_DELAY = 1000 / 60; -CDisasm::CDisasm(HINSTANCE _hInstance, HWND _hParent, DebugInterface *_cpu) : Dialog((LPCSTR)IDD_DISASM, _hInstance, _hParent) { +CDisasm::CDisasm(HINSTANCE _hInstance, HWND _hParent, MIPSDebugInterface *_cpu) : Dialog((LPCSTR)IDD_DISASM, _hInstance, _hParent) { cpu = _cpu; lastTicks_ = PSP_IsInited() ? CoreTiming::GetTicks() : 0; breakpoints_ = &g_breakpoints; - SetWindowText(m_hDlg, ConvertUTF8ToWString(_cpu->GetName()).c_str()); + SetWindowText(m_hDlg, L"R4"); RECT windowRect; GetWindowRect(m_hDlg,&windowRect); diff --git a/Windows/Debugger/Debugger_Disasm.h b/Windows/Debugger/Debugger_Disasm.h index faf986d4d511..3421ead3557d 100644 --- a/Windows/Debugger/Debugger_Disasm.h +++ b/Windows/Debugger/Debugger_Disasm.h @@ -17,7 +17,7 @@ class CDisasm : public Dialog { private: int minWidth; int minHeight; - DebugInterface *cpu; + MIPSDebugInterface *cpu; u64 lastTicks_; HWND statusBarWnd; @@ -45,7 +45,7 @@ class CDisasm : public Dialog { public: int index; - CDisasm(HINSTANCE _hInstance, HWND _hParent, DebugInterface *cpu); + CDisasm(HINSTANCE _hInstance, HWND _hParent, MIPSDebugInterface *cpu); ~CDisasm(); void Show(bool bShow, bool includeToTop = true) override; diff --git a/Windows/Debugger/Debugger_Lists.cpp b/Windows/Debugger/Debugger_Lists.cpp index 7329e30ada08..96cdda326ef3 100644 --- a/Windows/Debugger/Debugger_Lists.cpp +++ b/Windows/Debugger/Debugger_Lists.cpp @@ -265,7 +265,7 @@ const char* CtrlThreadList::getCurrentThreadName() // CtrlBreakpointList // -CtrlBreakpointList::CtrlBreakpointList(HWND hwnd, DebugInterface* cpu, CtrlDisAsmView* disasm) +CtrlBreakpointList::CtrlBreakpointList(HWND hwnd, MIPSDebugInterface* cpu, CtrlDisAsmView* disasm) : GenericListControl(hwnd,breakpointListDef),cpu(cpu),disasm(disasm) { SetSendInvalidRows(true); @@ -835,7 +835,7 @@ void CtrlWatchList::RefreshValues() { } uint32_t prevValue = watch.currentValue; - watch.evaluateFailed = !cpu_->parseExpression(watch.expression, watch.currentValue); + watch.evaluateFailed = !parseExpression(cpu_, watch.expression, watch.currentValue); if (prevValue != watch.currentValue) changes = true; } @@ -959,7 +959,7 @@ void CtrlWatchList::AddWatch() { WatchItemWindow win(nullptr, GetHandle(), cpu_); if (win.Exec()) { WatchInfo info; - if (cpu_->initExpression(win.GetExpression().c_str(), info.expression)) { + if (initExpression(cpu_, win.GetExpression().c_str(), info.expression)) { info.name = win.GetName(); info.originalExpression = win.GetExpression(); info.format = win.GetFormat(); @@ -978,7 +978,7 @@ void CtrlWatchList::EditWatch(int pos) { WatchItemWindow win(nullptr, GetHandle(), cpu_); win.Init(watch.name, watch.originalExpression, watch.format); if (win.Exec()) { - if (cpu_->initExpression(win.GetExpression().c_str(), watch.expression)) { + if (initExpression(cpu_, win.GetExpression().c_str(), watch.expression)) { watch.name = win.GetName(); watch.originalExpression = win.GetExpression(); watch.format = win.GetFormat(); diff --git a/Windows/Debugger/Debugger_Lists.h b/Windows/Debugger/Debugger_Lists.h index 0a8c7f52ce3d..bda6c1209f95 100644 --- a/Windows/Debugger/Debugger_Lists.h +++ b/Windows/Debugger/Debugger_Lists.h @@ -36,7 +36,7 @@ class CtrlDisAsmView; class CtrlBreakpointList: public GenericListControl { public: - CtrlBreakpointList(HWND hwnd, DebugInterface* cpu, CtrlDisAsmView* disasm); + CtrlBreakpointList(HWND hwnd, MIPSDebugInterface* cpu, CtrlDisAsmView* disasm); void reloadBreakpoints(); protected: bool WindowMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT &returnValue) override; @@ -49,7 +49,7 @@ class CtrlBreakpointList: public GenericListControl std::vector displayedBreakPoints_; std::vector displayedMemChecks_; std::wstring breakpointText; - DebugInterface* cpu; + MIPSDebugInterface* cpu; CtrlDisAsmView* disasm; void editBreakpoint(int itemIndex); diff --git a/Windows/Debugger/Debugger_MemoryDlg.cpp b/Windows/Debugger/Debugger_MemoryDlg.cpp index 7c320212ab5f..cc485ca0d7e6 100644 --- a/Windows/Debugger/Debugger_MemoryDlg.cpp +++ b/Windows/Debugger/Debugger_MemoryDlg.cpp @@ -54,11 +54,11 @@ LRESULT CALLBACK AddressEditProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM } -CMemoryDlg::CMemoryDlg(HINSTANCE _hInstance, HWND _hParent, DebugInterface *_cpu) : Dialog((LPCSTR)IDD_MEMORY, _hInstance,_hParent) +CMemoryDlg::CMemoryDlg(HINSTANCE _hInstance, HWND _hParent, MIPSDebugInterface *_cpu) : Dialog((LPCSTR)IDD_MEMORY, _hInstance,_hParent) { cpu = _cpu; wchar_t temp[256]; - wsprintf(temp,L"Memory Viewer - %S",cpu->GetName()); + wsprintf(temp,L"Memory Viewer - R4"); SetWindowText(m_hDlg,temp); ShowWindow(m_hDlg,SW_HIDE); diff --git a/Windows/Debugger/Debugger_MemoryDlg.h b/Windows/Debugger/Debugger_MemoryDlg.h index 9b216ba1e171..f5d8713e6f96 100644 --- a/Windows/Debugger/Debugger_MemoryDlg.h +++ b/Windows/Debugger/Debugger_MemoryDlg.h @@ -3,10 +3,11 @@ #include "Core/MemMap.h" -#include "Core/Debugger/DebugInterface.h" #include "CtrlMemView.h" #include "Common/CommonWindows.h" +class MIPSDebugInterface; + class CMemoryDlg : public Dialog { private: @@ -25,7 +26,7 @@ class CMemoryDlg : public Dialog void searchBoxRedraw(const std::vector &results); // constructor - CMemoryDlg(HINSTANCE _hInstance, HWND _hParent, DebugInterface *_cpu); + CMemoryDlg(HINSTANCE _hInstance, HWND _hParent, MIPSDebugInterface *_cpu); // destructor ~CMemoryDlg(void); diff --git a/Windows/Debugger/Debugger_VFPUDlg.cpp b/Windows/Debugger/Debugger_VFPUDlg.cpp index a82f0793e762..a20973c49199 100644 --- a/Windows/Debugger/Debugger_VFPUDlg.cpp +++ b/Windows/Debugger/Debugger_VFPUDlg.cpp @@ -15,7 +15,7 @@ CVFPUDlg::CVFPUDlg(HINSTANCE _hInstance, HWND _hParent, DebugInterface *cpu_) : { cpu = cpu_; wchar_t temp[256]; - wsprintf(temp, L"VFPU - %S", cpu->GetName()); + wsprintf(temp, L"VFPU - R4"); SetWindowText(m_hDlg,temp); ShowWindow(m_hDlg,SW_HIDE); diff --git a/Windows/Debugger/DumpMemoryWindow.cpp b/Windows/Debugger/DumpMemoryWindow.cpp index c09a3ce53384..2100add7fe03 100644 --- a/Windows/Debugger/DumpMemoryWindow.cpp +++ b/Windows/Debugger/DumpMemoryWindow.cpp @@ -6,6 +6,7 @@ #include "Core/HLE/ReplaceTables.h" #include "Core/MemMap.h" #include "Core/MIPS/JitCommon/JitBlockCache.h" +#include "Core/MIPS/MIPSDebugInterface.h" #include "Windows/Debugger/DumpMemoryWindow.h" #include "Windows/resource.h" #include "Windows/W32Util/ShellUtil.h" @@ -149,8 +150,8 @@ bool DumpMemoryWindow::fetchDialogData(HWND hwnd) // parse start address GetWindowTextA(GetDlgItem(hwnd,IDC_DUMP_STARTADDRESS),str,256); - if (cpu->initExpression(str,exp) == false - || cpu->parseExpression(exp,start) == false) + if (initExpression(cpu, str,exp) == false + || parseExpression(cpu, exp,start) == false) { snprintf(errorMessage, sizeof(errorMessage), "Invalid address expression \"%s\".",str); MessageBoxA(hwnd,errorMessage,"Error",MB_OK); @@ -159,8 +160,8 @@ bool DumpMemoryWindow::fetchDialogData(HWND hwnd) // parse size GetWindowTextA(GetDlgItem(hwnd,IDC_DUMP_SIZE),str,256); - if (cpu->initExpression(str,exp) == false - || cpu->parseExpression(exp,size) == false) + if (initExpression(cpu, str,exp) == false + || parseExpression(cpu, exp,size) == false) { snprintf(errorMessage, sizeof(errorMessage), "Invalid size expression \"%s\".",str); MessageBoxA(hwnd,errorMessage,"Error",MB_OK); diff --git a/Windows/Debugger/EditSymbolsWindow.cpp b/Windows/Debugger/EditSymbolsWindow.cpp index 607b67e3d517..0f9b797d7d0c 100644 --- a/Windows/Debugger/EditSymbolsWindow.cpp +++ b/Windows/Debugger/EditSymbolsWindow.cpp @@ -1,6 +1,7 @@ #include "EditSymbolsWindow.h" #include "../resource.h" +#include "Core/MIPS/MIPSDebugInterface.h" bool EditSymbolsWindow::GetCheckState(HWND hwnd, int dlgItem) { return SendMessage(GetDlgItem(hwnd, dlgItem), BM_GETCHECK, 0, 0) != 0; @@ -16,13 +17,13 @@ bool EditSymbolsWindow::fetchDialogData(HWND hwnd) // Parse the address GetWindowTextA(GetDlgItem(hwnd, IDC_EDITSYMBOLS_ADDRESS), str, 256); - if (cpu->initExpression(str, exp) == false) + if (initExpression(cpu, str, exp) == false) { snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError()); MessageBoxA(hwnd, errorMessage, "Error", MB_OK); return false; } - if (cpu->parseExpression(exp, address_) == false) + if (parseExpression(cpu, exp, address_) == false) { snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError()); MessageBoxA(hwnd, errorMessage, "Error", MB_OK); @@ -32,13 +33,13 @@ bool EditSymbolsWindow::fetchDialogData(HWND hwnd) // Parse the size GetWindowTextA(GetDlgItem(hwnd, IDC_EDITSYMBOLS_SIZE), str, 256); - if (cpu->initExpression(str, exp) == false) + if (initExpression(cpu, str, exp) == false) { snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError()); MessageBoxA(hwnd, errorMessage, "Error", MB_OK); return false; } - if (cpu->parseExpression(exp, size_) == false) + if (parseExpression(cpu, exp, size_) == false) { snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", str, getExpressionError()); MessageBoxA(hwnd, errorMessage, "Error", MB_OK); diff --git a/Windows/Debugger/WatchItemWindow.cpp b/Windows/Debugger/WatchItemWindow.cpp index c090c9337e7d..2c6af9dd55a7 100644 --- a/Windows/Debugger/WatchItemWindow.cpp +++ b/Windows/Debugger/WatchItemWindow.cpp @@ -106,7 +106,7 @@ bool WatchItemWindow::FetchDialogData(HWND hwnd) { GetWindowTextW(GetDlgItem(hwnd, IDC_BREAKPOINT_CONDITION), textValue, ARRAY_SIZE(textValue)); expression_ = ConvertWStringToUTF8(textValue); PostfixExpression compiled; - if (!cpu_->initExpression(expression_.c_str(), compiled)) { + if (!initExpression(cpu_, expression_.c_str(), compiled)) { char errorMessage[512]; snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", expression_.c_str(), getExpressionError()); MessageBoxA(hwnd, errorMessage, "Error", MB_OK); From 94da486da8f5de7f2ebe02df7881ab3be98e7d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 12 Dec 2024 19:04:01 +0100 Subject: [PATCH 23/58] DebugInterface const cleanup --- Core/Debugger/DebugInterface.h | 22 +++++++-------- Core/HLE/KernelThreadDebugInterface.h | 39 +++++++++------------------ Core/MIPS/MIPSDebugInterface.cpp | 8 +++--- Core/MIPS/MIPSDebugInterface.h | 24 ++++++++--------- 4 files changed, 39 insertions(+), 54 deletions(-) diff --git a/Core/Debugger/DebugInterface.h b/Core/Debugger/DebugInterface.h index 11559e20b7f8..a209c8fdeb7b 100644 --- a/Core/Debugger/DebugInterface.h +++ b/Core/Debugger/DebugInterface.h @@ -26,21 +26,19 @@ struct MemMap; class DebugInterface { public: - virtual u32 GetHi() = 0; - virtual u32 GetLo() = 0; - virtual u32 GetLLBit() = 0; - virtual u32 GetFPCond() = 0; + virtual u32 GetPC() const = 0; + virtual u32 GetRA() const = 0; + virtual u32 GetHi() const = 0; + virtual u32 GetLo() const = 0; + virtual u32 GetLLBit() const = 0; + virtual u32 GetFPCond() const = 0; + virtual u32 GetGPR32Value(int reg) const = 0; + virtual void SetPC(u32 _pc) = 0; virtual void SetHi(u32 val) = 0; virtual void SetLo(u32 val) = 0; - virtual u32 GetGPR32Value(int reg) = 0; - - virtual u32 GetPC() = 0; - virtual void SetPC(u32 _pc) = 0; - virtual u32 GetRA() = 0; - // More stuff for debugger - virtual u32 GetRegValue(int cat, int index) = 0; + virtual u32 GetRegValue(int cat, int index) const = 0; + virtual void PrintRegValue(int cat, int index, char *out, size_t outSize) const = 0; virtual void SetRegValue(int cat, int index, u32 value) {} - virtual void PrintRegValue(int cat, int index, char *out, size_t outSize) = 0; }; diff --git a/Core/HLE/KernelThreadDebugInterface.h b/Core/HLE/KernelThreadDebugInterface.h index dbab6d57e144..5e8cee5b2200 100644 --- a/Core/HLE/KernelThreadDebugInterface.h +++ b/Core/HLE/KernelThreadDebugInterface.h @@ -23,17 +23,20 @@ class KernelThreadDebugInterface : public DebugInterface { public: - KernelThreadDebugInterface(PSPThreadContext &t) : ctx(t) { - } - - u32 GetGPR32Value(int reg) override { return ctx.r[reg]; } - u32 GetPC() override { return ctx.pc; } - u32 GetRA() override { return ctx.r[MIPS_REG_RA]; } - u32 GetLLBit() override { return 0; } - u32 GetFPCond() override { return ctx.fpcond; } + KernelThreadDebugInterface(PSPThreadContext &t) : ctx(t) {} + + u32 GetGPR32Value(int reg) const override { return ctx.r[reg]; } + u32 GetHi() const override { return ctx.hi; } + u32 GetLo() const override { return ctx.lo; } + u32 GetPC() const override { return ctx.pc; } + u32 GetRA() const override { return ctx.r[MIPS_REG_RA]; } + u32 GetLLBit() const override { return 0; } + u32 GetFPCond() const override { return ctx.fpcond; } void SetPC(u32 _pc) override { ctx.pc = _pc; } + void SetHi(u32 val) override { ctx.hi = val; } + void SetLo(u32 val) override { ctx.lo = val; } - void PrintRegValue(int cat, int index, char *out, size_t outSize) override { + void PrintRegValue(int cat, int index, char *out, size_t outSize) const override { switch (cat) { case 0: snprintf(out, outSize, "%08X", ctx.r[index]); break; case 1: snprintf(out, outSize, "%f", ctx.f[index]); break; @@ -41,23 +44,7 @@ class KernelThreadDebugInterface : public DebugInterface { } } - u32 GetHi() override { - return ctx.hi; - } - - u32 GetLo() override { - return ctx.lo; - } - - void SetHi(u32 val) override { - ctx.hi = val; - } - - void SetLo(u32 val) override { - ctx.lo = val; - } - - u32 GetRegValue(int cat, int index) override { + u32 GetRegValue(int cat, int index) const override { switch (cat) { case 0: return ctx.r[index]; case 1: return ctx.fi[index]; diff --git a/Core/MIPS/MIPSDebugInterface.cpp b/Core/MIPS/MIPSDebugInterface.cpp index 650096975e4e..129b4cabfba2 100644 --- a/Core/MIPS/MIPSDebugInterface.cpp +++ b/Core/MIPS/MIPSDebugInterface.cpp @@ -54,7 +54,7 @@ enum ReferenceIndexType { class MipsExpressionFunctions : public IExpressionFunctions { public: - MipsExpressionFunctions(DebugInterface *_cpu): cpu(_cpu) {} + MipsExpressionFunctions(const DebugInterface *_cpu): cpu(_cpu) {} bool parseReference(char* str, uint32_t& referenceIndex) override { @@ -199,7 +199,7 @@ class MipsExpressionFunctions : public IExpressionFunctions { } private: - DebugInterface *cpu; + const DebugInterface *cpu; }; unsigned int MIPSDebugInterface::readMemory(unsigned int address) { @@ -282,12 +282,12 @@ std::string MIPSDebugInterface::GetRegName(int cat, int index) { return "???"; } -bool initExpression(DebugInterface *debug, const char* exp, PostfixExpression& dest) { +bool initExpression(const DebugInterface *debug, const char* exp, PostfixExpression& dest) { MipsExpressionFunctions funcs(debug); return initPostfixExpression(exp, &funcs, dest); } -bool parseExpression(DebugInterface *debug, PostfixExpression& exp, u32& dest) { +bool parseExpression(const DebugInterface *debug, PostfixExpression& exp, u32& dest) { MipsExpressionFunctions funcs(debug); return parsePostfixExpression(exp, &funcs, dest); } diff --git a/Core/MIPS/MIPSDebugInterface.h b/Core/MIPS/MIPSDebugInterface.h index d6dc40963f6e..f71687316bd0 100644 --- a/Core/MIPS/MIPSDebugInterface.h +++ b/Core/MIPS/MIPSDebugInterface.h @@ -39,13 +39,13 @@ class MIPSDebugInterface : public DebugInterface int getColor(unsigned int address, bool darkMode) const; std::string getDescription(unsigned int address); - u32 GetGPR32Value(int reg) override { return cpu->r[reg]; } - float GetFPR32Value(int reg) { return cpu->f[reg]; } + u32 GetGPR32Value(int reg) const override { return cpu->r[reg]; } + float GetFPR32Value(int reg) const { return cpu->f[reg]; } void SetGPR32Value(int reg, u32 value) { cpu->r[reg] = value; } - u32 GetPC() override { return cpu->pc; } - u32 GetRA() override { return cpu->r[MIPS_REG_RA]; } - u32 GetFPCond() override { return cpu->fpcond; } + u32 GetPC() const override { return cpu->pc; } + u32 GetRA() const override { return cpu->r[MIPS_REG_RA]; } + u32 GetFPCond() const override { return cpu->fpcond; } void SetPC(u32 _pc) override { cpu->pc = _pc; } static const char *GetCategoryName(int cat) { @@ -59,7 +59,7 @@ class MIPSDebugInterface : public DebugInterface } static std::string GetRegName(int cat, int index); - void PrintRegValue(int cat, int index, char *out, size_t outSize) override { + void PrintRegValue(int cat, int index, char *out, size_t outSize) const override { switch (cat) { case 0: snprintf(out, outSize, "%08X", cpu->r[index]); break; case 1: snprintf(out, outSize, "%f", cpu->f[index]); break; @@ -67,15 +67,15 @@ class MIPSDebugInterface : public DebugInterface } } - u32 GetHi() override { + u32 GetHi() const override { return cpu->hi; } - u32 GetLLBit() override { + u32 GetLLBit() const override { return cpu->llBit; } - u32 GetLo() override { + u32 GetLo() const override { return cpu->lo; } @@ -87,7 +87,7 @@ class MIPSDebugInterface : public DebugInterface cpu->lo = val; } - u32 GetRegValue(int cat, int index) override { + u32 GetRegValue(int cat, int index) const override { switch (cat) { case 0: return cpu->r[index]; @@ -124,6 +124,6 @@ class MIPSDebugInterface : public DebugInterface } }; -bool initExpression(DebugInterface *debug, const char* exp, PostfixExpression& dest); -bool parseExpression(DebugInterface *debug, PostfixExpression& exp, u32& dest); +bool initExpression(const DebugInterface *debug, const char* exp, PostfixExpression& dest); +bool parseExpression(const DebugInterface *debug, PostfixExpression& exp, u32& dest); void DisAsm(u32 pc, char *out, size_t outSize); From 2c19bf25257a1617197a496bb19436f3a45e0543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 12 Dec 2024 19:05:33 +0100 Subject: [PATCH 24/58] More const cleanup --- UI/ImDebugger/ImDebugger.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 435d761137b9..1b471448439f 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -112,7 +112,7 @@ void DrawSchedulerView(ImConfig &cfg) { ImGui::End(); } -void DrawRegisterView(ImConfig &config, ImControl &control, MIPSDebugInterface *mipsDebug) { +void DrawRegisterView(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug) { ImGui::SetNextWindowSize(ImVec2(320, 600), ImGuiCond_FirstUseEver); if (!ImGui::Begin("Registers", &config.regsOpen)) { ImGui::End(); @@ -743,7 +743,7 @@ void DrawAudioChannels(ImConfig &cfg) { ImGui::End(); } -void DrawCallStacks(MIPSDebugInterface *debug, bool *open) { +static void DrawCallStacks(const MIPSDebugInterface *debug, bool *open) { if (!ImGui::Begin("Callstacks", open)) { ImGui::End(); return; @@ -799,7 +799,7 @@ void DrawCallStacks(MIPSDebugInterface *debug, bool *open) { ImGui::End(); } -void DrawModules(MIPSDebugInterface *debug, ImConfig &cfg) { +static void DrawModules(const MIPSDebugInterface *debug, ImConfig &cfg) { if (!ImGui::Begin("Modules", &cfg.modulesOpen) || !g_symbolMap) { ImGui::End(); return; From 597be1c9bc6c8e6ed022777af31f7c0a2b63ade7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 12 Dec 2024 19:24:02 +0100 Subject: [PATCH 25/58] Stop pretending that DisassemblyManager isn't a singleton - it currently is. --- Core/Debugger/DisassemblyManager.cpp | 30 +++---- Core/Debugger/DisassemblyManager.h | 19 +++-- .../WebSocket/BreakpointSubscriber.cpp | 3 +- Core/Debugger/WebSocket/DisasmSubscriber.cpp | 26 +++--- Core/Debugger/WebSocket/HLESubscriber.cpp | 12 +-- .../Debugger/WebSocket/SteppingSubscriber.cpp | 10 +-- UI/ImDebugger/ImDebugger.cpp | 2 +- UI/ImDebugger/ImDisasmView.cpp | 82 +++++++++---------- UI/ImDebugger/ImDisasmView.h | 15 ++-- Windows/Debugger/CtrlDisAsmView.cpp | 80 +++++++++--------- Windows/Debugger/CtrlDisAsmView.h | 19 ++--- Windows/Debugger/EditSymbolsWindow.cpp | 3 +- 12 files changed, 143 insertions(+), 158 deletions(-) diff --git a/Core/Debugger/DisassemblyManager.cpp b/Core/Debugger/DisassemblyManager.cpp index a7cc6c4abfd6..b2d74ef5a192 100644 --- a/Core/Debugger/DisassemblyManager.cpp +++ b/Core/Debugger/DisassemblyManager.cpp @@ -36,10 +36,7 @@ #include "Core/Debugger/SymbolMap.h" #include "Core/Debugger/DisassemblyManager.h" -std::map DisassemblyManager::entries; -std::recursive_mutex DisassemblyManager::entriesLock_; -DebugInterface* DisassemblyManager::cpu_; -int DisassemblyManager::maxParamChars = 29; +DisassemblyManager g_disassemblyManager; bool isInInterval(u32 start, u32 size, u32 value) { return start <= value && value <= (start+size-1); @@ -185,6 +182,11 @@ std::map::iterator findDisassemblyEntry(std::map guard(lock_); - DebugInterface *cpu = DisassemblyManager::getCpu(); + DebugInterface *cpu = g_disassemblyManager.getCpu(); for (u32 funcPos = address; funcPos < end; funcPos += 4) { MIPSAnalyst::MipsOpcodeInfo opInfo = MIPSAnalyst::GetOpcodeInfo(cpu, funcPos); @@ -612,7 +614,7 @@ void DisassemblyFunction::load() } } - DebugInterface *cpu = DisassemblyManager::getCpu(); + DebugInterface *cpu = g_disassemblyManager.getCpu(); u32 funcPos = address; u32 funcEnd = address+size; @@ -747,11 +749,7 @@ void DisassemblyFunction::clear() hash = 0; } -bool DisassemblyOpcode::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug) -{ - if (!cpuDebug) - cpuDebug = DisassemblyManager::getCpu(); - +bool DisassemblyOpcode::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug) { char opcode[64],arguments[256]; char dizz[512]; DisAsm(address, dizz, sizeof(dizz)); @@ -778,7 +776,7 @@ void DisassemblyOpcode::getBranchLines(u32 start, u32 size, std::vector getBranchLines(u32 start, u32 size); @@ -212,14 +212,17 @@ class DisassemblyManager { u32 getNthPreviousAddress(u32 address, int n = 1); u32 getNthNextAddress(u32 address, int n = 1); - static DebugInterface *getCpu() { return cpu_; }; - static int getMaxParamChars() { return maxParamChars; }; + DebugInterface *getCpu() { return cpu_; }; + int getMaxParamChars() { return maxParamChars; }; + private: - static std::map entries; - static std::recursive_mutex entriesLock_; - static DebugInterface *cpu_; - static int maxParamChars; + std::map entries; + std::recursive_mutex entriesLock_; + DebugInterface *cpu_; + int maxParamChars = 29; }; +extern DisassemblyManager g_disassemblyManager; + bool isInInterval(u32 start, u32 size, u32 value); bool IsLikelyStringAt(uint32_t addr); diff --git a/Core/Debugger/WebSocket/BreakpointSubscriber.cpp b/Core/Debugger/WebSocket/BreakpointSubscriber.cpp index 2f3b788b3473..f5d75f40235e 100644 --- a/Core/Debugger/WebSocket/BreakpointSubscriber.cpp +++ b/Core/Debugger/WebSocket/BreakpointSubscriber.cpp @@ -227,9 +227,8 @@ void WebSocketCPUBreakpointList(DebuggerRequest &req) { else json.writeString("symbol", symbol); - DisassemblyManager manager; DisassemblyLineInfo line; - manager.getLine(manager.getStartAddress(bp.addr), true, line); + g_disassemblyManager.getLine(g_disassemblyManager.getStartAddress(bp.addr), true, line, currentDebugMIPS); json.writeString("code", line.name + " " + line.params); json.pop(); diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp index 023fc989556e..697aab37550c 100644 --- a/Core/Debugger/WebSocket/DisasmSubscriber.cpp +++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp @@ -34,10 +34,10 @@ class WebSocketDisasmState : public DebuggerSubscriber { public: WebSocketDisasmState() { - disasm_.setCpu(currentDebugMIPS); + g_disassemblyManager.setCpu(currentDebugMIPS); } ~WebSocketDisasmState() { - disasm_.clear(); + g_disassemblyManager.clear(); } void Base(DebuggerRequest &req); @@ -48,8 +48,6 @@ class WebSocketDisasmState : public DebuggerSubscriber { protected: void WriteDisasmLine(JsonWriter &json, const DisassemblyLineInfo &l); void WriteBranchGuide(JsonWriter &json, const BranchLine &l); - - DisassemblyManager disasm_; }; DebuggerSubscriber *WebSocketDisasmInit(DebuggerEventHandlerMap &map) { @@ -316,18 +314,18 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { if (count != 0) { count = std::min(count, MAX_RANGE); // Let's assume everything is two instructions. - disasm_.analyze(start - 4, count * 8 + 8); - start = disasm_.getStartAddress(start); + g_disassemblyManager.analyze(start - 4, count * 8 + 8); + start = g_disassemblyManager.getStartAddress(start); if (start == -1) req.ParamU32("address", &start); - end = disasm_.getNthNextAddress(start, count); + end = g_disassemblyManager.getNthNextAddress(start, count); } else if (req.ParamU32("end", &end)) { end = std::max(start, end); if (end - start > MAX_RANGE * 4) end = start + MAX_RANGE * 4; // Let's assume everything is two instructions at most. - disasm_.analyze(start - 4, end - start + 8); - start = disasm_.getStartAddress(start); + g_disassemblyManager.analyze(start - 4, end - start + 8); + start = g_disassemblyManager.getStartAddress(start); if (start == -1) req.ParamU32("address", &start); // Correct end and calculate count based on it. @@ -336,11 +334,11 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { u32 next = start; count = 0; if (stop < start) { - for (next = start; next > stop; next = disasm_.getNthNextAddress(next, 1)) { + for (next = start; next > stop; next = g_disassemblyManager.getNthNextAddress(next, 1)) { count++; } } - for (end = next; end < stop && end >= next; end = disasm_.getNthNextAddress(end, 1)) { + for (end = next; end < stop && end >= next; end = g_disassemblyManager.getNthNextAddress(end, 1)) { count++; } } else { @@ -362,7 +360,7 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { DisassemblyLineInfo line; uint32_t addr = start; for (uint32_t i = 0; i < count; ++i) { - disasm_.getLine(addr, displaySymbols, line, cpuDebug); + g_disassemblyManager.getLine(addr, displaySymbols, line, cpuDebug); WriteDisasmLine(json, line); addr += line.totalSize; @@ -373,7 +371,7 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { json.pop(); json.pushArray("branchGuides"); - auto branchGuides = disasm_.getBranchLines(start, end - start); + auto branchGuides = g_disassemblyManager.getBranchLines(start, end - start); for (auto bl : branchGuides) WriteBranchGuide(json, bl); json.pop(); @@ -428,7 +426,7 @@ void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) { bool found = false; uint32_t addr = start; do { - disasm_.getLine(addr, displaySymbols, line, cpuDebug); + g_disassemblyManager.getLine(addr, displaySymbols, line, cpuDebug); const std::string addressSymbol = g_symbolMap->GetLabelString(addr); std::string mergeForSearch; diff --git a/Core/Debugger/WebSocket/HLESubscriber.cpp b/Core/Debugger/WebSocket/HLESubscriber.cpp index 8e749f19f5e3..110483ed3e6a 100644 --- a/Core/Debugger/WebSocket/HLESubscriber.cpp +++ b/Core/Debugger/WebSocket/HLESubscriber.cpp @@ -297,8 +297,7 @@ void WebSocketHLEFuncAdd(DebuggerRequest &req) { } // Clear cache for branch lines and such. - DisassemblyManager manager; - manager.clear(); + g_disassemblyManager.clear(); JsonWriter &json = req.Respond(); json.writeUint("address", addr); @@ -354,8 +353,7 @@ void WebSocketHLEFuncRemove(DebuggerRequest &req) { } // Clear cache for branch lines and such. - DisassemblyManager manager; - manager.clear(); + g_disassemblyManager.clear(); JsonWriter &json = req.Respond(); json.writeUint("address", funcBegin); @@ -392,8 +390,7 @@ static u32 RemoveFuncSymbolsInRange(u32 addr, u32 size) { } // Clear cache for branch lines and such. - DisassemblyManager manager; - manager.clear(); + g_disassemblyManager.clear(); } return counter; } @@ -592,9 +589,8 @@ void WebSocketHLEBacktrace(DebuggerRequest &req) { json.writeUint("sp", f.sp); json.writeUint("stackSize", f.stackSize); - DisassemblyManager manager; DisassemblyLineInfo line; - manager.getLine(manager.getStartAddress(f.pc), true, line, cpuDebug); + g_disassemblyManager.getLine(g_disassemblyManager.getStartAddress(f.pc), true, line, cpuDebug); json.writeString("code", line.name + " " + line.params); json.pop(); diff --git a/Core/Debugger/WebSocket/SteppingSubscriber.cpp b/Core/Debugger/WebSocket/SteppingSubscriber.cpp index 4f7662bada8e..d596f2038c82 100644 --- a/Core/Debugger/WebSocket/SteppingSubscriber.cpp +++ b/Core/Debugger/WebSocket/SteppingSubscriber.cpp @@ -30,10 +30,10 @@ using namespace MIPSAnalyst; struct WebSocketSteppingState : public DebuggerSubscriber { WebSocketSteppingState() { - disasm_.setCpu(currentDebugMIPS); + g_disassemblyManager.setCpu(currentDebugMIPS); } ~WebSocketSteppingState() { - disasm_.clear(); + g_disassemblyManager.clear(); } void Into(DebuggerRequest &req); @@ -47,8 +47,6 @@ struct WebSocketSteppingState : public DebuggerSubscriber { int GetNextInstructionCount(DebugInterface *cpuDebug); void PrepareResume(); void AddThreadCondition(uint32_t breakpointAddress, uint32_t threadID); - - DisassemblyManager disasm_; }; DebuggerSubscriber *WebSocketSteppingInit(DebuggerEventHandlerMap &map) { @@ -266,8 +264,8 @@ void WebSocketSteppingState::HLE(DebuggerRequest &req) { } uint32_t WebSocketSteppingState::GetNextAddress(DebugInterface *cpuDebug) { - uint32_t current = disasm_.getStartAddress(cpuDebug->GetPC()); - return disasm_.getNthNextAddress(current, 1); + uint32_t current = g_disassemblyManager.getStartAddress(cpuDebug->GetPC()); + return g_disassemblyManager.getNthNextAddress(current, 1); } int WebSocketSteppingState::GetNextInstructionCount(DebugInterface *cpuDebug) { diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 1b471448439f..5d206b06452b 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -892,7 +892,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu } // TODO: Pass mipsDebug in where needed instead. - DisassemblyManager::setCpu(mipsDebug); + g_disassemblyManager.setCpu(mipsDebug); disasm_.View().setDebugger(mipsDebug); for (int i = 0; i < 4; i++) { mem_[i].View().setDebugger(mipsDebug); diff --git a/UI/ImDebugger/ImDisasmView.cpp b/UI/ImDebugger/ImDisasmView.cpp index e0d53513f74b..606faea5f12d 100644 --- a/UI/ImDebugger/ImDisasmView.cpp +++ b/UI/ImDebugger/ImDisasmView.cpp @@ -33,11 +33,11 @@ ImDisasmView::ImDisasmView() { } ImDisasmView::~ImDisasmView() { - manager.clear(); + g_disassemblyManager.clear(); } void ImDisasmView::ScanVisibleFunctions() { - manager.analyze(windowStart_, manager.getNthNextAddress(windowStart_, visibleRows_) - windowStart_); + g_disassemblyManager.analyze(windowStart_, g_disassemblyManager.getNthNextAddress(windowStart_, visibleRows_) - windowStart_); } static ImColor scaleColor(ImColor color, float factor) { @@ -130,7 +130,7 @@ void ImDisasmView::assembleOpcode(u32 address, const std::string &defaultText) { ScanVisibleFunctions(); if (address == curAddress) - gotoAddr(manager.getNthNextAddress(curAddress, 1)); + gotoAddr(g_disassemblyManager.getNthNextAddress(curAddress, 1)); redraw(); } else { @@ -141,7 +141,7 @@ void ImDisasmView::assembleOpcode(u32 address, const std::string &defaultText) { } void ImDisasmView::drawBranchLine(ImDrawList *drawList, Bounds rect, std::map &addressPositions, const BranchLine &line) { - u32 windowEnd = manager.getNthNextAddress(windowStart_, visibleRows_); + u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart_, visibleRows_); float topY; float bottomY; @@ -235,7 +235,7 @@ std::set ImDisasmView::getSelectedLineArguments() { std::set args; DisassemblyLineInfo line; for (u32 addr = selectRangeStart_; addr < selectRangeEnd_; addr += 4) { - manager.getLine(addr, displaySymbols_, line); + g_disassemblyManager.getLine(addr, displaySymbols_, line, debugger_); size_t p = 0, nextp = line.params.find(','); while (nextp != line.params.npos) { args.emplace(line.params.substr(p, nextp - p)); @@ -350,7 +350,7 @@ void ImDisasmView::Draw(ImDrawList *drawList, ImControl &control) { const u32 pc = debugger_->GetPC(); for (int i = 0; i < visibleRows_; i++) { - manager.getLine(address, displaySymbols_, line); + g_disassemblyManager.getLine(address, displaySymbols_, line, debugger_); float rowY1 = rowHeight_ * i; float rowY2 = rowHeight_ * (i + 1); @@ -413,7 +413,7 @@ void ImDisasmView::Draw(ImDrawList *drawList, ImControl &control) { address += line.totalSize; } - std::vector branchLines = manager.getBranchLines(windowStart_, address - windowStart_); + std::vector branchLines = g_disassemblyManager.getBranchLines(windowStart_, address - windowStart_); for (size_t i = 0; i < branchLines.size(); i++) { drawBranchLine(drawList, bounds, addressPositions, branchLines[i]); } @@ -443,9 +443,9 @@ void ImDisasmView::Draw(ImDrawList *drawList, ImControl &control) { if (is_hovered) { if (io.MouseWheel > 0.0f) { // TODO: Scale steps by the value. - windowStart_ = manager.getNthPreviousAddress(windowStart_, 4); + windowStart_ = g_disassemblyManager.getNthPreviousAddress(windowStart_, 4); } else if (io.MouseWheel < 0.0f) { - windowStart_ = manager.getNthNextAddress(windowStart_, 4); + windowStart_ = g_disassemblyManager.getNthNextAddress(windowStart_, 4); } } @@ -479,9 +479,9 @@ void ImDisasmView::NotifyStep() { void ImDisasmView::ScrollRelative(int amount) { if (amount > 0) { - windowStart_ = manager.getNthNextAddress(windowStart_, amount); + windowStart_ = g_disassemblyManager.getNthNextAddress(windowStart_, amount); } else if (amount < 0) { - windowStart_ = manager.getNthPreviousAddress(windowStart_, amount); + windowStart_ = g_disassemblyManager.getNthPreviousAddress(windowStart_, amount); } ScanVisibleFunctions(); } @@ -489,7 +489,7 @@ void ImDisasmView::ScrollRelative(int amount) { // Follows branches and jumps. void ImDisasmView::FollowBranch() { DisassemblyLineInfo line; - manager.getLine(curAddress_, true, line); + g_disassemblyManager.getLine(curAddress_, true, line, debugger_); if (line.type == DISTYPE_OPCODE || line.type == DISTYPE_MACRO) { if (line.info.isBranch) { @@ -551,7 +551,7 @@ void ImDisasmView::ProcessKeyboardShortcuts(bool focused) { return; } - u32 windowEnd = manager.getNthNextAddress(windowStart_, visibleRows_); + u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart_, visibleRows_); keyTaken = true; ImGuiIO& io = ImGui::GetIO(); @@ -591,27 +591,27 @@ void ImDisasmView::ProcessKeyboardShortcuts(bool focused) { ScanVisibleFunctions(); } if (ImGui::IsKeyPressed(ImGuiKey_PageDown)) { - setCurAddress(manager.getNthPreviousAddress(windowEnd, 1), (io.KeyMods & ImGuiMod_Shift) != 0); + setCurAddress(g_disassemblyManager.getNthPreviousAddress(windowEnd, 1), (io.KeyMods & ImGuiMod_Shift) != 0); } if (ImGui::IsKeyPressed(ImGuiKey_PageUp)) { setCurAddress(windowStart_, ImGui::IsKeyDown(ImGuiKey_LeftShift)); } } else { if (ImGui::IsKeyPressed(ImGuiKey_PageDown)) { - windowStart_ = manager.getNthNextAddress(windowStart_, visibleRows_); + windowStart_ = g_disassemblyManager.getNthNextAddress(windowStart_, visibleRows_); } if (ImGui::IsKeyPressed(ImGuiKey_PageUp)) { - windowStart_ = manager.getNthPreviousAddress(windowStart_, visibleRows_); + windowStart_ = g_disassemblyManager.getNthPreviousAddress(windowStart_, visibleRows_); } if (ImGui::IsKeyPressed(ImGuiKey_F3)) { SearchNext(!ImGui::IsKeyPressed(ImGuiKey_LeftShift)); } if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) { - setCurAddress(manager.getNthNextAddress(curAddress_, 1), (io.KeyMods & ImGuiMod_Shift) != 0); + setCurAddress(g_disassemblyManager.getNthNextAddress(curAddress_, 1), (io.KeyMods & ImGuiMod_Shift) != 0); scrollAddressIntoView(); } if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) { - setCurAddress(manager.getNthPreviousAddress(curAddress_, 1), (io.KeyMods & ImGuiMod_Shift) != 0); + setCurAddress(g_disassemblyManager.getNthPreviousAddress(curAddress_, 1), (io.KeyMods & ImGuiMod_Shift) != 0); scrollAddressIntoView(); } if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) { @@ -637,11 +637,11 @@ void ImDisasmView::ProcessKeyboardShortcuts(bool focused) { } else { switch (wParam & 0xFFFF) { case VK_NEXT: - if (manager.getNthNextAddress(curAddress, 1) != windowEnd && curAddressIsVisible()) { - setCurAddress(manager.getNthPreviousAddress(windowEnd, 1), KeyDownAsync(VK_SHIFT)); + if (g_disassemblyManager.getNthNextAddress(curAddress, 1) != windowEnd && curAddressIsVisible()) { + setCurAddress(g_disassemblyManager.getNthPreviousAddress(windowEnd, 1), KeyDownAsync(VK_SHIFT)); scrollAddressIntoView(); } else { - setCurAddress(manager.getNthNextAddress(windowEnd, visibleRows_ - 1), KeyDownAsync(VK_SHIFT)); + setCurAddress(g_disassemblyManager.getNthNextAddress(windowEnd, visibleRows_ - 1), KeyDownAsync(VK_SHIFT)); scrollAddressIntoView(); } break; @@ -650,7 +650,7 @@ void ImDisasmView::ProcessKeyboardShortcuts(bool focused) { setCurAddress(windowStart_, KeyDownAsync(VK_SHIFT)); scrollAddressIntoView(); } else { - setCurAddress(manager.getNthPreviousAddress(windowStart_, visibleRows_), KeyDownAsync(VK_SHIFT)); + setCurAddress(g_disassemblyManager.getNthPreviousAddress(windowStart_, visibleRows_), KeyDownAsync(VK_SHIFT)); scrollAddressIntoView(); } break; @@ -666,18 +666,18 @@ void ImDisasmView::ProcessKeyboardShortcuts(bool focused) { } void ImDisasmView::scrollAddressIntoView() { - u32 windowEnd = manager.getNthNextAddress(windowStart_, visibleRows_); + u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart_, visibleRows_); if (curAddress_ < windowStart_) windowStart_ = curAddress_; else if (curAddress_ >= windowEnd) - windowStart_ = manager.getNthPreviousAddress(curAddress_, visibleRows_ - 1); + windowStart_ = g_disassemblyManager.getNthPreviousAddress(curAddress_, visibleRows_ - 1); ScanVisibleFunctions(); } bool ImDisasmView::curAddressIsVisible() { - u32 windowEnd = manager.getNthNextAddress(windowStart_, visibleRows_); + u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart_, visibleRows_); return curAddress_ >= windowStart_ && curAddress_ < windowEnd; } @@ -828,7 +828,7 @@ void ImDisasmView::PopupMenu(ImControl &control) { g_symbolMap->RemoveFunction(funcBegin, true); g_symbolMap->SortSymbols(); - manager.clear(); + g_disassemblyManager.clear(); mapReloaded_ = true; } else { @@ -852,7 +852,7 @@ void ImDisasmView::PopupMenu(ImControl &control) { snprintf(symname, 128, "u_un_%08X", curAddress_); g_symbolMap->AddFunction(symname, curAddress_, newSize); g_symbolMap->SortSymbols(); - manager.clear(); + g_disassemblyManager.clear(); mapReloaded_ = true; } @@ -902,7 +902,7 @@ void ImDisasmView::updateStatusBarText() { char text[512]; DisassemblyLineInfo line; - manager.getLine(curAddress_, true, line); + g_disassemblyManager.getLine(curAddress_, true, line, debugger_); text[0] = 0; if (line.type == DISTYPE_OPCODE || line.type == DISTYPE_MACRO) { @@ -997,7 +997,7 @@ void ImDisasmView::updateStatusBarText() { u32 ImDisasmView::yToAddress(float y) { int line = (int)(y / rowHeight_); - return manager.getNthNextAddress(windowStart_, line); + return g_disassemblyManager.getNthNextAddress(windowStart_, line); } void ImDisasmView::calculatePixelPositions() { @@ -1022,7 +1022,7 @@ void ImDisasmView::SearchNext(bool forward) { } // Note: Search will replace matchAddress_ with the current address. - u32 searchAddress = manager.getNthNextAddress(matchAddress_, 1); + u32 searchAddress = g_disassemblyManager.getNthNextAddress(matchAddress_, 1); // limit address to sensible ranges if (searchAddress < 0x04000000) @@ -1038,7 +1038,7 @@ void ImDisasmView::SearchNext(bool forward) { DisassemblyLineInfo lineInfo; while (searchAddress < 0x0A000000) { - manager.getLine(searchAddress, displaySymbols_, lineInfo); + g_disassemblyManager.getLine(searchAddress, displaySymbols_, lineInfo, debugger_); char addressText[64]; getDisasmAddressText(searchAddress, addressText, true, lineInfo.type == DISTYPE_OPCODE); @@ -1071,7 +1071,7 @@ void ImDisasmView::SearchNext(bool forward) { return; } - searchAddress = manager.getNthNextAddress(searchAddress, 1); + searchAddress = g_disassemblyManager.getNthNextAddress(searchAddress, 1); if (searchAddress >= 0x04200000 && searchAddress < 0x08000000) searchAddress = 0x08000000; } @@ -1102,7 +1102,7 @@ std::string ImDisasmView::disassembleRange(u32 start, u32 size) { while (disAddress < start + size) { char addressText[64], buffer[512]; - manager.getLine(disAddress, displaySymbols_, line); + g_disassemblyManager.getLine(disAddress, displaySymbols_, line, debugger_); bool isLabel = getDisasmAddressText(disAddress, addressText, false, line.type == DISTYPE_OPCODE); if (isLabel) { @@ -1162,23 +1162,23 @@ void ImDisasmView::disassembleToFile() { // get size void ImDisasmView::getOpcodeText(u32 address, char* dest, int bufsize) { DisassemblyLineInfo line; - address = manager.getStartAddress(address); - manager.getLine(address, displaySymbols_, line); + address = g_disassemblyManager.getStartAddress(address); + g_disassemblyManager.getLine(address, displaySymbols_, line, debugger_); snprintf(dest, bufsize, "%s %s", line.name.c_str(), line.params.c_str()); } void ImDisasmView::scrollStepping(u32 newPc) { - u32 windowEnd = manager.getNthNextAddress(windowStart_, visibleRows_); + u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart_, visibleRows_); - newPc = manager.getStartAddress(newPc); - if (newPc >= windowEnd || newPc >= manager.getNthPreviousAddress(windowEnd, 1)) + newPc = g_disassemblyManager.getStartAddress(newPc); + if (newPc >= windowEnd || newPc >= g_disassemblyManager.getNthPreviousAddress(windowEnd, 1)) { - windowStart_ = manager.getNthPreviousAddress(newPc, visibleRows_ - 2); + windowStart_ = g_disassemblyManager.getNthPreviousAddress(newPc, visibleRows_ - 2); } } u32 ImDisasmView::getInstructionSizeAt(u32 address) { - u32 start = manager.getStartAddress(address); - u32 next = manager.getNthNextAddress(start, 1); + u32 start = g_disassemblyManager.getStartAddress(address); + u32 next = g_disassemblyManager.getNthNextAddress(start, 1); return next - address; } diff --git a/UI/ImDebugger/ImDisasmView.h b/UI/ImDebugger/ImDisasmView.h index 7ebe2adf10fc..f859e3a4f048 100644 --- a/UI/ImDebugger/ImDisasmView.h +++ b/UI/ImDebugger/ImDisasmView.h @@ -40,7 +40,7 @@ class ImDisasmView { void scrollAddressIntoView(); bool curAddressIsVisible(); void ScanVisibleFunctions(); - void clearFunctions() { manager.clear(); }; + void clearFunctions() { g_disassemblyManager.clear(); }; void getOpcodeText(u32 address, char *dest, int bufsize); u32 yToAddress(float y); @@ -49,7 +49,7 @@ class ImDisasmView { if (debugger_ != deb) { debugger_ = deb; curAddress_ = debugger_->GetPC(); - manager.setCpu(deb); + g_disassemblyManager.setCpu(deb); } } @@ -63,11 +63,11 @@ class ImDisasmView { void gotoAddr(unsigned int addr) { if (positionLocked_ != 0) return; - u32 windowEnd = manager.getNthNextAddress(windowStart_, visibleRows_); - u32 newAddress = manager.getStartAddress(addr); + u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart_, visibleRows_); + u32 newAddress = g_disassemblyManager.getStartAddress(addr); if (newAddress < windowStart_ || newAddress >= windowEnd) { - windowStart_ = manager.getNthPreviousAddress(newAddress, visibleRows_ / 2); + windowStart_ = g_disassemblyManager.getNthPreviousAddress(newAddress, visibleRows_ / 2); } setCurAddress(newAddress); @@ -89,8 +89,8 @@ class ImDisasmView { void editBreakpoint(ImConfig &cfg); void setCurAddress(u32 newAddress, bool extend = false) { - newAddress = manager.getStartAddress(newAddress); - const u32 after = manager.getNthNextAddress(newAddress, 1); + newAddress = g_disassemblyManager.getStartAddress(newAddress); + const u32 after = g_disassemblyManager.getNthNextAddress(newAddress, 1); curAddress_ = newAddress; selectRangeStart_ = extend ? std::min(selectRangeStart_, newAddress) : newAddress; selectRangeEnd_ = extend ? std::max(selectRangeEnd_, after) : after; @@ -137,7 +137,6 @@ class ImDisasmView { std::set getSelectedLineArguments(); void drawArguments(ImDrawList *list, Bounds rc, const DisassemblyLineInfo &line, float x, float y, ImColor textColor, const std::set ¤tArguments); - DisassemblyManager manager; u32 curAddress_ = 0; u32 selectRangeStart_ = 0; u32 selectRangeEnd_ = 0; diff --git a/Windows/Debugger/CtrlDisAsmView.cpp b/Windows/Debugger/CtrlDisAsmView.cpp index cfc3ad04a842..71d05e81d654 100644 --- a/Windows/Debugger/CtrlDisAsmView.cpp +++ b/Windows/Debugger/CtrlDisAsmView.cpp @@ -63,7 +63,7 @@ void CtrlDisAsmView::deinit() void CtrlDisAsmView::scanVisibleFunctions() { - manager.analyze(windowStart,manager.getNthNextAddress(windowStart,visibleRows)-windowStart); + g_disassemblyManager.analyze(windowStart, g_disassemblyManager.getNthNextAddress(windowStart,visibleRows)-windowStart); } LRESULT CALLBACK CtrlDisAsmView::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) @@ -206,7 +206,7 @@ CtrlDisAsmView::~CtrlDisAsmView() { DeleteObject(font); DeleteObject(boldfont); - manager.clear(); + g_disassemblyManager.clear(); } static COLORREF scaleColor(COLORREF color, float factor) @@ -326,7 +326,7 @@ void CtrlDisAsmView::assembleOpcode(u32 address, const std::string &defaultText) scanVisibleFunctions(); if (address == curAddress) - gotoAddr(manager.getNthNextAddress(curAddress,1)); + gotoAddr(g_disassemblyManager.getNthNextAddress(curAddress,1)); redraw(); } else { @@ -337,7 +337,7 @@ void CtrlDisAsmView::assembleOpcode(u32 address, const std::string &defaultText) void CtrlDisAsmView::drawBranchLine(HDC hdc, std::map &addressPositions, const BranchLine &line) { HPEN pen; - u32 windowEnd = manager.getNthNextAddress(windowStart,visibleRows); + u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows); int topY; int bottomY; @@ -433,7 +433,7 @@ std::set CtrlDisAsmView::getSelectedLineArguments() { DisassemblyLineInfo line; for (u32 addr = selectRangeStart; addr < selectRangeEnd; addr += 4) { - manager.getLine(addr, displaySymbols, line); + g_disassemblyManager.getLine(addr, displaySymbols, line, debugger); size_t p = 0, nextp = line.params.find(','); while (nextp != line.params.npos) { args.emplace(line.params.substr(p, nextp - p)); @@ -520,7 +520,7 @@ void CtrlDisAsmView::onPaint(WPARAM wParam, LPARAM lParam) DisassemblyLineInfo line; for (int i = 0; i < visibleRows; i++) { - manager.getLine(address,displaySymbols,line); + g_disassemblyManager.getLine(address,displaySymbols,line, debugger); int rowY1 = rowHeight*i; int rowY2 = rowHeight*(i+1); @@ -594,7 +594,7 @@ void CtrlDisAsmView::onPaint(WPARAM wParam, LPARAM lParam) address += line.totalSize; } - std::vector branchLines = manager.getBranchLines(windowStart,address-windowStart); + std::vector branchLines = g_disassemblyManager.getBranchLines(windowStart,address-windowStart); for (size_t i = 0; i < branchLines.size(); i++) { drawBranchLine(hdc,addressPositions,branchLines[i]); @@ -627,16 +627,16 @@ void CtrlDisAsmView::onVScroll(WPARAM wParam, LPARAM lParam) switch (wParam & 0xFFFF) { case SB_LINEDOWN: - windowStart = manager.getNthNextAddress(windowStart,1); + windowStart = g_disassemblyManager.getNthNextAddress(windowStart,1); break; case SB_LINEUP: - windowStart = manager.getNthPreviousAddress(windowStart,1); + windowStart = g_disassemblyManager.getNthPreviousAddress(windowStart,1); break; case SB_PAGEDOWN: - windowStart = manager.getNthNextAddress(windowStart,visibleRows); + windowStart = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows); break; case SB_PAGEUP: - windowStart = manager.getNthPreviousAddress(windowStart,visibleRows); + windowStart = g_disassemblyManager.getNthPreviousAddress(windowStart,visibleRows); break; default: return; @@ -649,7 +649,7 @@ void CtrlDisAsmView::onVScroll(WPARAM wParam, LPARAM lParam) void CtrlDisAsmView::followBranch() { DisassemblyLineInfo line; - manager.getLine(curAddress,true,line); + g_disassemblyManager.getLine(curAddress, true, line, debugger); if (line.type == DISTYPE_OPCODE || line.type == DISTYPE_MACRO) { @@ -721,7 +721,7 @@ void CtrlDisAsmView::onKeyDown(WPARAM wParam, LPARAM lParam) return; dontRedraw = false; - u32 windowEnd = manager.getNthNextAddress(windowStart,visibleRows); + u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows); keyTaken = true; if (KeyDownAsync(VK_CONTROL)) @@ -764,7 +764,7 @@ void CtrlDisAsmView::onKeyDown(WPARAM wParam, LPARAM lParam) scanVisibleFunctions(); break; case VK_NEXT: - setCurAddress(manager.getNthPreviousAddress(windowEnd,1),KeyDownAsync(VK_SHIFT)); + setCurAddress(g_disassemblyManager.getNthPreviousAddress(windowEnd,1),KeyDownAsync(VK_SHIFT)); break; case VK_PRIOR: setCurAddress(windowStart,KeyDownAsync(VK_SHIFT)); @@ -774,19 +774,19 @@ void CtrlDisAsmView::onKeyDown(WPARAM wParam, LPARAM lParam) switch (wParam & 0xFFFF) { case VK_DOWN: - setCurAddress(manager.getNthNextAddress(curAddress,1), KeyDownAsync(VK_SHIFT)); + setCurAddress(g_disassemblyManager.getNthNextAddress(curAddress,1), KeyDownAsync(VK_SHIFT)); scrollAddressIntoView(); break; case VK_UP: - setCurAddress(manager.getNthPreviousAddress(curAddress,1), KeyDownAsync(VK_SHIFT)); + setCurAddress(g_disassemblyManager.getNthPreviousAddress(curAddress,1), KeyDownAsync(VK_SHIFT)); scrollAddressIntoView(); break; case VK_NEXT: - if (manager.getNthNextAddress(curAddress,1) != windowEnd && curAddressIsVisible()) { - setCurAddress(manager.getNthPreviousAddress(windowEnd,1), KeyDownAsync(VK_SHIFT)); + if (g_disassemblyManager.getNthNextAddress(curAddress,1) != windowEnd && curAddressIsVisible()) { + setCurAddress(g_disassemblyManager.getNthPreviousAddress(windowEnd,1), KeyDownAsync(VK_SHIFT)); scrollAddressIntoView(); } else { - setCurAddress(manager.getNthNextAddress(windowEnd,visibleRows-1), KeyDownAsync(VK_SHIFT)); + setCurAddress(g_disassemblyManager.getNthNextAddress(windowEnd,visibleRows-1), KeyDownAsync(VK_SHIFT)); scrollAddressIntoView(); } break; @@ -795,7 +795,7 @@ void CtrlDisAsmView::onKeyDown(WPARAM wParam, LPARAM lParam) setCurAddress(windowStart, KeyDownAsync(VK_SHIFT)); scrollAddressIntoView(); } else { - setCurAddress(manager.getNthPreviousAddress(windowStart,visibleRows), KeyDownAsync(VK_SHIFT)); + setCurAddress(g_disassemblyManager.getNthPreviousAddress(windowStart,visibleRows), KeyDownAsync(VK_SHIFT)); scrollAddressIntoView(); } break; @@ -836,19 +836,19 @@ void CtrlDisAsmView::onKeyUp(WPARAM wParam, LPARAM lParam) void CtrlDisAsmView::scrollAddressIntoView() { - u32 windowEnd = manager.getNthNextAddress(windowStart,visibleRows); + u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows); if (curAddress < windowStart) windowStart = curAddress; else if (curAddress >= windowEnd) - windowStart = manager.getNthPreviousAddress(curAddress,visibleRows-1); + windowStart = g_disassemblyManager.getNthPreviousAddress(curAddress,visibleRows-1); scanVisibleFunctions(); } bool CtrlDisAsmView::curAddressIsVisible() { - u32 windowEnd = manager.getNthNextAddress(windowStart,visibleRows); + u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows); return curAddress >= windowStart && curAddress < windowEnd; } @@ -1060,7 +1060,7 @@ void CtrlDisAsmView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) g_symbolMap->RemoveFunction(funcBegin,true); g_symbolMap->SortSymbols(); - manager.clear(); + g_disassemblyManager.clear(); SendMessage(GetParent(wnd), WM_DEB_MAPLOADED, 0, 0); } @@ -1094,7 +1094,7 @@ void CtrlDisAsmView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) snprintf(symname,128,"u_un_%08X",curAddress); g_symbolMap->AddFunction(symname,curAddress,newSize); g_symbolMap->SortSymbols(); - manager.clear(); + g_disassemblyManager.clear(); SendMessage(GetParent(wnd), WM_DEB_MAPLOADED, 0, 0); } @@ -1143,7 +1143,7 @@ void CtrlDisAsmView::updateStatusBarText() char text[512]; DisassemblyLineInfo line; - manager.getLine(curAddress,true,line); + g_disassemblyManager.getLine(curAddress,true,line, debugger); text[0] = 0; if (line.type == DISTYPE_OPCODE || line.type == DISTYPE_MACRO) @@ -1242,7 +1242,7 @@ void CtrlDisAsmView::updateStatusBarText() u32 CtrlDisAsmView::yToAddress(int y) { int line = y/rowHeight; - return manager.getNthNextAddress(windowStart,line); + return g_disassemblyManager.getNthNextAddress(windowStart,line); } void CtrlDisAsmView::calculatePixelPositions() @@ -1272,9 +1272,9 @@ void CtrlDisAsmView::search(bool continueSearch) searchQuery[i] = tolower(searchQuery[i]); } SetFocus(wnd); - searchAddress = manager.getNthNextAddress(curAddress,1); + searchAddress = g_disassemblyManager.getNthNextAddress(curAddress,1); } else { - searchAddress = manager.getNthNextAddress(matchAddress,1); + searchAddress = g_disassemblyManager.getNthNextAddress(matchAddress,1); } // limit address to sensible ranges @@ -1291,7 +1291,7 @@ void CtrlDisAsmView::search(bool continueSearch) DisassemblyLineInfo lineInfo; while (searchAddress < 0x0A000000) { - manager.getLine(searchAddress,displaySymbols,lineInfo); + g_disassemblyManager.getLine(searchAddress,displaySymbols,lineInfo, debugger); char addressText[64]; getDisasmAddressText(searchAddress,addressText,true,lineInfo.type == DISTYPE_OPCODE); @@ -1326,7 +1326,7 @@ void CtrlDisAsmView::search(bool continueSearch) return; } - searchAddress = manager.getNthNextAddress(searchAddress,1); + searchAddress = g_disassemblyManager.getNthNextAddress(searchAddress,1); if (searchAddress >= 0x04200000 && searchAddress < 0x08000000) searchAddress = 0x08000000; } @@ -1361,7 +1361,7 @@ std::string CtrlDisAsmView::disassembleRange(u32 start, u32 size) { char addressText[64],buffer[512]; - manager.getLine(disAddress,displaySymbols,line); + g_disassemblyManager.getLine(disAddress,displaySymbols,line, debugger); bool isLabel = getDisasmAddressText(disAddress,addressText,false,line.type == DISTYPE_OPCODE); if (isLabel) @@ -1423,25 +1423,25 @@ void CtrlDisAsmView::disassembleToFile() { void CtrlDisAsmView::getOpcodeText(u32 address, char* dest, int bufsize) { DisassemblyLineInfo line; - address = manager.getStartAddress(address); - manager.getLine(address,displaySymbols,line); + address = g_disassemblyManager.getStartAddress(address); + g_disassemblyManager.getLine(address,displaySymbols,line, debugger); snprintf(dest, bufsize, "%s %s",line.name.c_str(),line.params.c_str()); } void CtrlDisAsmView::scrollStepping(u32 newPc) { - u32 windowEnd = manager.getNthNextAddress(windowStart,visibleRows); + u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows); - newPc = manager.getStartAddress(newPc); - if (newPc >= windowEnd || newPc >= manager.getNthPreviousAddress(windowEnd,1)) + newPc = g_disassemblyManager.getStartAddress(newPc); + if (newPc >= windowEnd || newPc >= g_disassemblyManager.getNthPreviousAddress(windowEnd,1)) { - windowStart = manager.getNthPreviousAddress(newPc,visibleRows-2); + windowStart = g_disassemblyManager.getNthPreviousAddress(newPc,visibleRows-2); } } u32 CtrlDisAsmView::getInstructionSizeAt(u32 address) { - u32 start = manager.getStartAddress(address); - u32 next = manager.getNthNextAddress(start,1); + u32 start = g_disassemblyManager.getStartAddress(address); + u32 next = g_disassemblyManager.getNthNextAddress(start,1); return next - address; } diff --git a/Windows/Debugger/CtrlDisAsmView.h b/Windows/Debugger/CtrlDisAsmView.h index 5bb06ef651ce..aa8541349c63 100644 --- a/Windows/Debugger/CtrlDisAsmView.h +++ b/Windows/Debugger/CtrlDisAsmView.h @@ -29,7 +29,6 @@ class CtrlDisAsmView HFONT boldfont; RECT rect; - DisassemblyManager manager; u32 curAddress; u32 selectRangeStart; u32 selectRangeEnd; @@ -101,7 +100,7 @@ class CtrlDisAsmView bool curAddressIsVisible(); void redraw(); void scanVisibleFunctions(); - void clearFunctions() { manager.clear(); }; + void clearFunctions() { g_disassemblyManager.clear(); }; void getOpcodeText(u32 address, char* dest, int bufsize); int getRowHeight() { return rowHeight; }; @@ -112,7 +111,7 @@ class CtrlDisAsmView { debugger=deb; curAddress=debugger->GetPC(); - manager.setCpu(deb); + g_disassemblyManager.setCpu(deb); } DebugInterface *getDebugger() { @@ -126,12 +125,12 @@ class CtrlDisAsmView { if (positionLocked_ != 0) return; - u32 windowEnd = manager.getNthNextAddress(windowStart,visibleRows); - u32 newAddress = manager.getStartAddress(addr); + u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows); + u32 newAddress = g_disassemblyManager.getStartAddress(addr); if (newAddress < windowStart || newAddress >= windowEnd) { - windowStart = manager.getNthPreviousAddress(newAddress,visibleRows/2); + windowStart = g_disassemblyManager.getNthPreviousAddress(newAddress,visibleRows/2); } setCurAddress(newAddress); @@ -158,9 +157,9 @@ class CtrlDisAsmView void scrollWindow(int lines) { if (lines < 0) - windowStart = manager.getNthPreviousAddress(windowStart,abs(lines)); + windowStart = g_disassemblyManager.getNthPreviousAddress(windowStart,abs(lines)); else - windowStart = manager.getNthNextAddress(windowStart,lines); + windowStart = g_disassemblyManager.getNthNextAddress(windowStart,lines); scanVisibleFunctions(); redraw(); @@ -168,8 +167,8 @@ class CtrlDisAsmView void setCurAddress(u32 newAddress, bool extend = false) { - newAddress = manager.getStartAddress(newAddress); - const u32 after = manager.getNthNextAddress(newAddress,1); + newAddress = g_disassemblyManager.getStartAddress(newAddress); + const u32 after = g_disassemblyManager.getNthNextAddress(newAddress,1); curAddress = newAddress; selectRangeStart = extend ? std::min(selectRangeStart, newAddress) : newAddress; selectRangeEnd = extend ? std::max(selectRangeEnd, after) : after; diff --git a/Windows/Debugger/EditSymbolsWindow.cpp b/Windows/Debugger/EditSymbolsWindow.cpp index 0f9b797d7d0c..a50144b67b7a 100644 --- a/Windows/Debugger/EditSymbolsWindow.cpp +++ b/Windows/Debugger/EditSymbolsWindow.cpp @@ -178,8 +178,7 @@ void EditSymbolsWindow::Remove() { } // Clear cache for branch lines and such. - DisassemblyManager manager; - manager.clear(); + g_disassemblyManager.clear(); } } From 4b36664dd6c37afb6061fe999c830290315e6db3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Dec 2024 13:36:25 +0100 Subject: [PATCH 26/58] Move more things to State.cpp/h, break out some stuff into a function --- GPU/Debugger/State.cpp | 182 +++++++++++++++++++++ GPU/Debugger/State.h | 10 +- Windows/GEDebugger/GEDebugger.cpp | 5 +- Windows/GEDebugger/GEDebugger.h | 2 +- Windows/GEDebugger/VertexPreview.cpp | 230 ++++----------------------- 5 files changed, 228 insertions(+), 201 deletions(-) diff --git a/GPU/Debugger/State.cpp b/GPU/Debugger/State.cpp index 4db525f9c481..b242c515f6ba 100644 --- a/GPU/Debugger/State.cpp +++ b/GPU/Debugger/State.cpp @@ -6,6 +6,7 @@ #include "GPU/Common/GPUDebugInterface.h" #include "GPU/GeDisasm.h" #include "GPU/Common/VertexDecoderCommon.h" +#include "GPU/Common/SplineCommon.h" #include "Core/System.h" void FormatStateRow(GPUDebugInterface *gpudebug, char *dest, size_t destSize, CmdFormatType fmt, u32 value, bool enabled, u32 otherValue, u32 otherValue2) { @@ -646,3 +647,184 @@ static void FormatVertColRawColor(char *dest, size_t destSize, const void *data, break; } } + + +static void SwapUVs(GPUDebugVertex &a, GPUDebugVertex &b) { + float tempu = a.u; + float tempv = a.v; + a.u = b.u; + a.v = b.v; + b.u = tempu; + b.v = tempv; +} + +static void RotateUVThrough(GPUDebugVertex v[4]) { + float x1 = v[2].x; + float x2 = v[0].x; + float y1 = v[2].y; + float y2 = v[0].y; + + if ((x1 < x2 && y1 > y2) || (x1 > x2 && y1 < y2)) + SwapUVs(v[1], v[3]); +} + +void ExpandRectangles(std::vector &vertices, std::vector &indices, int &count, bool throughMode) { + static std::vector newVerts; + static std::vector newInds; + + bool useInds = true; + size_t numInds = indices.size(); + if (indices.empty()) { + useInds = false; + numInds = count; + } + + //rectangles always need 2 vertices, disregard the last one if there's an odd number + numInds = numInds & ~1; + + // Will need 4 coords and 6 points per rectangle (currently 2 each.) + newVerts.resize(numInds * 2); + newInds.resize(numInds * 3); + + u16 v = 0; + GPUDebugVertex *vert = &newVerts[0]; + u16 *ind = &newInds[0]; + for (size_t i = 0; i < numInds; i += 2) { + const auto &orig_tl = useInds ? vertices[indices[i + 0]] : vertices[i + 0]; + const auto &orig_br = useInds ? vertices[indices[i + 1]] : vertices[i + 1]; + + vert[0] = orig_br; + + // Top right. + vert[1] = orig_br; + vert[1].y = orig_tl.y; + vert[1].v = orig_tl.v; + + vert[2] = orig_tl; + + // Bottom left. + vert[3] = orig_br; + vert[3].x = orig_tl.x; + vert[3].u = orig_tl.u; + + // That's the four corners. Now process UV rotation. + // This is the same for through and non-through, since it's already transformed. + RotateUVThrough(vert); + + // Build the two 3 point triangles from our 4 coordinates. + *ind++ = v + 0; + *ind++ = v + 1; + *ind++ = v + 2; + *ind++ = v + 3; + *ind++ = v + 0; + *ind++ = v + 2; + + vert += 4; + v += 4; + } + + std::swap(vertices, newVerts); + std::swap(indices, newInds); + count *= 3; +} + +void ExpandBezier(int &count, int op, const std::vector &simpleVerts, const std::vector &indices, std::vector &generatedVerts, std::vector &generatedInds) { + using namespace Spline; + + int count_u = (op >> 0) & 0xFF; + int count_v = (op >> 8) & 0xFF; + // Real hardware seems to draw nothing when given < 4 either U or V. + if (count_u < 4 || count_v < 4) + return; + + BezierSurface surface; + surface.num_points_u = count_u; + surface.num_points_v = count_v; + surface.tess_u = gstate.getPatchDivisionU(); + surface.tess_v = gstate.getPatchDivisionV(); + surface.num_patches_u = (count_u - 1) / 3; + surface.num_patches_v = (count_v - 1) / 3; + surface.primType = gstate.getPatchPrimitiveType(); + surface.patchFacing = false; + + int num_points = count_u * count_v; + // Make an array of pointers to the control points, to get rid of indices. + std::vector points(num_points); + for (int idx = 0; idx < num_points; idx++) + points[idx] = simpleVerts.data() + (!indices.empty() ? indices[idx] : idx); + + int total_patches = surface.num_patches_u * surface.num_patches_v; + generatedVerts.resize((surface.tess_u + 1) * (surface.tess_v + 1) * total_patches); + generatedInds.resize(surface.tess_u * surface.tess_v * 6 * total_patches); + + OutputBuffers output; + output.vertices = generatedVerts.data(); + output.indices = generatedInds.data(); + output.count = 0; + + ControlPoints cpoints; + cpoints.pos = new Vec3f[num_points]; + cpoints.tex = new Vec2f[num_points]; + cpoints.col = new Vec4f[num_points]; + cpoints.Convert(points.data(), num_points); + + surface.Init((int)generatedVerts.size()); + SoftwareTessellation(output, surface, gstate.vertType, cpoints); + count = output.count; + + delete[] cpoints.pos; + delete[] cpoints.tex; + delete[] cpoints.col; +} + +void ExpandSpline(int &count, int op, const std::vector &simpleVerts, const std::vector &indices, std::vector &generatedVerts, std::vector &generatedInds) { + using namespace Spline; + + int count_u = (op >> 0) & 0xFF; + int count_v = (op >> 8) & 0xFF; + // Real hardware seems to draw nothing when given < 4 either U or V. + if (count_u < 4 || count_v < 4) + return; + + SplineSurface surface; + surface.num_points_u = count_u; + surface.num_points_v = count_v; + surface.tess_u = gstate.getPatchDivisionU(); + surface.tess_v = gstate.getPatchDivisionV(); + surface.type_u = (op >> 16) & 0x3; + surface.type_v = (op >> 18) & 0x3; + surface.num_patches_u = count_u - 3; + surface.num_patches_v = count_v - 3; + surface.primType = gstate.getPatchPrimitiveType(); + surface.patchFacing = false; + + int num_points = count_u * count_v; + // Make an array of pointers to the control points, to get rid of indices. + std::vector points(num_points); + for (int idx = 0; idx < num_points; idx++) + points[idx] = simpleVerts.data() + (!indices.empty() ? indices[idx] : idx); + + int patch_div_s = surface.num_patches_u * surface.tess_u; + int patch_div_t = surface.num_patches_v * surface.tess_v; + generatedVerts.resize((patch_div_s + 1) * (patch_div_t + 1)); + generatedInds.resize(patch_div_s * patch_div_t * 6); + + OutputBuffers output; + output.vertices = generatedVerts.data(); + output.indices = generatedInds.data(); + output.count = 0; + + ControlPoints cpoints; + cpoints.pos = (Vec3f *)AllocateAlignedMemory(sizeof(Vec3f) * num_points, 16); + cpoints.tex = (Vec2f *)AllocateAlignedMemory(sizeof(Vec2f) * num_points, 16); + cpoints.col = (Vec4f *)AllocateAlignedMemory(sizeof(Vec4f) * num_points, 16); + cpoints.Convert(points.data(), num_points); + + surface.Init((int)generatedVerts.size()); + SoftwareTessellation(output, surface, gstate.vertType, cpoints); + count = output.count; + + FreeAlignedMemory(cpoints.pos); + FreeAlignedMemory(cpoints.tex); + FreeAlignedMemory(cpoints.col); +} diff --git a/GPU/Debugger/State.h b/GPU/Debugger/State.h index 916c568b14e5..715610d3e0c9 100644 --- a/GPU/Debugger/State.h +++ b/GPU/Debugger/State.h @@ -7,7 +7,7 @@ #include "Common/CommonTypes.h" #include "GPU/Debugger/GECommandTable.h" -// Extracted from Windows/GE Debugger/TabState.cpp +#include "GPU/Common/SplineCommon.h" enum VertexListCols { VERTEXLIST_COL_X, @@ -39,3 +39,11 @@ class VertexDecoder; void FormatStateRow(GPUDebugInterface *debug, char *dest, size_t destSize, CmdFormatType fmt, u32 value, bool enabled, u32 otherValue, u32 otherValue2); void FormatVertCol(char *dest, size_t destSize, const GPUDebugVertex &vert, int col); void FormatVertColRaw(VertexDecoder *decoder, char *dest, size_t destSize, int row, int col); + + +// These are utilities used by the debugger vertex preview. + +// Later I hope to re-use more of the real logic. +void ExpandBezier(int &count, int op, const std::vector &simpleVerts, const std::vector &indices, std::vector &generatedVerts, std::vector &generatedInds); +void ExpandSpline(int &count, int op, const std::vector &simpleVerts, const std::vector &indices, std::vector &generatedVerts, std::vector &generatedInds); +void ExpandRectangles(std::vector &vertices, std::vector &indices, int &count, bool throughMode); diff --git a/Windows/GEDebugger/GEDebugger.cpp b/Windows/GEDebugger/GEDebugger.cpp index 7004ed80b7bb..19bf460eff4b 100644 --- a/Windows/GEDebugger/GEDebugger.cpp +++ b/Windows/GEDebugger/GEDebugger.cpp @@ -568,7 +568,10 @@ void CGEDebugger::UpdatePreviews() { UpdatePrimaryPreview(state); UpdateSecondPreview(state); - u32 primOp = PrimPreviewOp(); + u32 primOp = 0; + if (!showClut_) { + primOp = PrimPreviewOp(); + } if (primOp != 0) { UpdatePrimPreview(primOp, 3); } diff --git a/Windows/GEDebugger/GEDebugger.h b/Windows/GEDebugger/GEDebugger.h index bb747f332e3f..bb8b8e08ac4a 100644 --- a/Windows/GEDebugger/GEDebugger.h +++ b/Windows/GEDebugger/GEDebugger.h @@ -102,7 +102,7 @@ class CGEDebugger : public Dialog { void UpdatePreviews(); void UpdatePrimaryPreview(const GPUgstate &state); void UpdateSecondPreview(const GPUgstate &state); - u32 PrimPreviewOp(); + static u32 PrimPreviewOp(); void UpdatePrimPreview(u32 op, int which); void CleanupPrimPreview(); void HandleRedraw(int which); diff --git a/Windows/GEDebugger/VertexPreview.cpp b/Windows/GEDebugger/VertexPreview.cpp index f2c9e1bd5502..a0715bc5f0b1 100644 --- a/Windows/GEDebugger/VertexPreview.cpp +++ b/Windows/GEDebugger/VertexPreview.cpp @@ -25,6 +25,7 @@ #include "GPU/GPUCommon.h" #include "GPU/Common/GPUDebugInterface.h" #include "GPU/Common/SplineCommon.h" +#include "GPU/Debugger/State.h" #include "GPU/GPUState.h" #include "Common/Log.h" #include "Common/MemoryUtil.h" @@ -73,202 +74,20 @@ static void BindPreviewProgram(GLSLProgram *&prog) { glsl_bind(prog); } -static void SwapUVs(GPUDebugVertex &a, GPUDebugVertex &b) { - float tempu = a.u; - float tempv = a.v; - a.u = b.u; - a.v = b.v; - b.u = tempu; - b.v = tempv; -} - -static void RotateUVThrough(GPUDebugVertex v[4]) { - float x1 = v[2].x; - float x2 = v[0].x; - float y1 = v[2].y; - float y2 = v[0].y; - - if ((x1 < x2 && y1 > y2) || (x1 > x2 && y1 < y2)) - SwapUVs(v[1], v[3]); -} - -static void ExpandRectangles(std::vector &vertices, std::vector &indices, int &count, bool throughMode) { - static std::vector newVerts; - static std::vector newInds; - - bool useInds = true; - size_t numInds = indices.size(); - if (indices.empty()) { - useInds = false; - numInds = count; - } - - //rectangles always need 2 vertices, disregard the last one if there's an odd number - numInds = numInds & ~1; - - // Will need 4 coords and 6 points per rectangle (currently 2 each.) - newVerts.resize(numInds * 2); - newInds.resize(numInds * 3); - - u16 v = 0; - GPUDebugVertex *vert = &newVerts[0]; - u16 *ind = &newInds[0]; - for (size_t i = 0; i < numInds; i += 2) { - const auto &orig_tl = useInds ? vertices[indices[i + 0]] : vertices[i + 0]; - const auto &orig_br = useInds ? vertices[indices[i + 1]] : vertices[i + 1]; - - vert[0] = orig_br; - - // Top right. - vert[1] = orig_br; - vert[1].y = orig_tl.y; - vert[1].v = orig_tl.v; - - vert[2] = orig_tl; - - // Bottom left. - vert[3] = orig_br; - vert[3].x = orig_tl.x; - vert[3].u = orig_tl.u; - - // That's the four corners. Now process UV rotation. - // This is the same for through and non-through, since it's already transformed. - RotateUVThrough(vert); - - // Build the two 3 point triangles from our 4 coordinates. - *ind++ = v + 0; - *ind++ = v + 1; - *ind++ = v + 2; - *ind++ = v + 3; - *ind++ = v + 0; - *ind++ = v + 2; - - vert += 4; - v += 4; - } - - std::swap(vertices, newVerts); - std::swap(indices, newInds); - count *= 3; -} - u32 CGEDebugger::PrimPreviewOp() { DisplayList list; - if (gpuDebug != nullptr && gpuDebug->GetCurrentDisplayList(list) && !showClut_) { + if (gpuDebug != nullptr && gpuDebug->GetCurrentDisplayList(list)) { const u32 op = Memory::Read_U32(list.pc); const u32 cmd = op >> 24; if (cmd == GE_CMD_PRIM || cmd == GE_CMD_BEZIER || cmd == GE_CMD_SPLINE) { return op; } } - return 0; } -static void ExpandBezier(int &count, int op, const std::vector &simpleVerts, const std::vector &indices, std::vector &generatedVerts, std::vector &generatedInds) { - using namespace Spline; - - int count_u = (op >> 0) & 0xFF; - int count_v = (op >> 8) & 0xFF; - // Real hardware seems to draw nothing when given < 4 either U or V. - if (count_u < 4 || count_v < 4) - return; - - BezierSurface surface; - surface.num_points_u = count_u; - surface.num_points_v = count_v; - surface.tess_u = gstate.getPatchDivisionU(); - surface.tess_v = gstate.getPatchDivisionV(); - surface.num_patches_u = (count_u - 1) / 3; - surface.num_patches_v = (count_v - 1) / 3; - surface.primType = gstate.getPatchPrimitiveType(); - surface.patchFacing = false; - - int num_points = count_u * count_v; - // Make an array of pointers to the control points, to get rid of indices. - std::vector points(num_points); - for (int idx = 0; idx < num_points; idx++) - points[idx] = simpleVerts.data() + (!indices.empty() ? indices[idx] : idx); - - int total_patches = surface.num_patches_u * surface.num_patches_v; - generatedVerts.resize((surface.tess_u + 1) * (surface.tess_v + 1) * total_patches); - generatedInds.resize(surface.tess_u * surface.tess_v * 6 * total_patches); - - OutputBuffers output; - output.vertices = generatedVerts.data(); - output.indices = generatedInds.data(); - output.count = 0; - - ControlPoints cpoints; - cpoints.pos = new Vec3f[num_points]; - cpoints.tex = new Vec2f[num_points]; - cpoints.col = new Vec4f[num_points]; - cpoints.Convert(points.data(), num_points); - - surface.Init((int)generatedVerts.size()); - SoftwareTessellation(output, surface, gstate.vertType, cpoints); - count = output.count; - - delete [] cpoints.pos; - delete [] cpoints.tex; - delete [] cpoints.col; -} - -static void ExpandSpline(int &count, int op, const std::vector &simpleVerts, const std::vector &indices, std::vector &generatedVerts, std::vector &generatedInds) { - using namespace Spline; - - int count_u = (op >> 0) & 0xFF; - int count_v = (op >> 8) & 0xFF; - // Real hardware seems to draw nothing when given < 4 either U or V. - if (count_u < 4 || count_v < 4) - return; - - SplineSurface surface; - surface.num_points_u = count_u; - surface.num_points_v = count_v; - surface.tess_u = gstate.getPatchDivisionU(); - surface.tess_v = gstate.getPatchDivisionV(); - surface.type_u = (op >> 16) & 0x3; - surface.type_v = (op >> 18) & 0x3; - surface.num_patches_u = count_u - 3; - surface.num_patches_v = count_v - 3; - surface.primType = gstate.getPatchPrimitiveType(); - surface.patchFacing = false; - - int num_points = count_u * count_v; - // Make an array of pointers to the control points, to get rid of indices. - std::vector points(num_points); - for (int idx = 0; idx < num_points; idx++) - points[idx] = simpleVerts.data() + (!indices.empty() ? indices[idx] : idx); - - int patch_div_s = surface.num_patches_u * surface.tess_u; - int patch_div_t = surface.num_patches_v * surface.tess_v; - generatedVerts.resize((patch_div_s + 1) * (patch_div_t + 1)); - generatedInds.resize(patch_div_s * patch_div_t * 6); - - OutputBuffers output; - output.vertices = generatedVerts.data(); - output.indices = generatedInds.data(); - output.count = 0; - - ControlPoints cpoints; - cpoints.pos = (Vec3f *)AllocateAlignedMemory(sizeof(Vec3f) * num_points, 16); - cpoints.tex = (Vec2f *)AllocateAlignedMemory(sizeof(Vec2f) * num_points, 16); - cpoints.col = (Vec4f *)AllocateAlignedMemory(sizeof(Vec4f) * num_points, 16); - cpoints.Convert(points.data(), num_points); - - surface.Init((int)generatedVerts.size()); - SoftwareTessellation(output, surface, gstate.vertType, cpoints); - count = output.count; - - FreeAlignedMemory(cpoints.pos); - FreeAlignedMemory(cpoints.tex); - FreeAlignedMemory(cpoints.col); -} - -void CGEDebugger::UpdatePrimPreview(u32 op, int which) { +bool GetPrimPreview(u32 op, int which, GEPrimitiveType &prim, std::vector &vertices, std::vector &indices, int &count) { u32 prim_type = GE_PRIM_INVALID; - int count = 0; int count_u = 0; int count_v = 0; @@ -287,24 +106,21 @@ void CGEDebugger::UpdatePrimPreview(u32 op, int which) { if (prim_type >= 7) { ERROR_LOG(Log::G3D, "Unsupported prim type: %x", op); - return; + return false; } if (!gpuDebug) { ERROR_LOG(Log::G3D, "Invalid debugging environment, shutting down?"); - return; + return false; } - which &= previewsEnabled_; if (count == 0 || which == 0) { - return; + return false; } - const GEPrimitiveType prim = static_cast(prim_type); - static std::vector vertices; - static std::vector indices; + prim = static_cast(prim_type); if (!gpuDebug->GetCurrentSimpleVertices(count, vertices, indices)) { ERROR_LOG(Log::G3D, "Vertex preview not yet supported"); - return; + return false; } if (cmd != GE_CMD_PRIM) { @@ -341,9 +157,6 @@ void CGEDebugger::UpdatePrimPreview(u32 op, int which) { ExpandRectangles(vertices, indices, count, gpuDebug->GetGState().isModeThrough()); } - float fw, fh; - float x, y; - // TODO: Probably there's a better way and place to do this. u16 minIndex = 0; u16 maxIndex = count - 1; @@ -372,8 +185,6 @@ void CGEDebugger::UpdatePrimPreview(u32 op, int which) { const float invTexWidth = 1.0f / gpuDebug->GetGState().getTextureWidth(0); const float invTexHeight = 1.0f / gpuDebug->GetGState().getTextureHeight(0); - const float invRealTexWidth = 1.0f / gstate_c.curTextureWidth; - const float invRealTexHeight = 1.0f / gstate_c.curTextureHeight; bool clampS = gpuDebug->GetGState().isTexCoordClampedS(); bool clampT = gpuDebug->GetGState().isTexCoordClampedT(); for (u16 i = minIndex; i <= maxIndex; ++i) { @@ -385,6 +196,28 @@ void CGEDebugger::UpdatePrimPreview(u32 op, int which) { wrapCoord(vertices[i].v); } + return true; +} + +void CGEDebugger::UpdatePrimPreview(u32 op, int which) { + which &= previewsEnabled_; + + static std::vector vertices; + static std::vector indices; + + int count = 0; + GEPrimitiveType prim; + if (!GetPrimPreview(op, which, prim, vertices, indices, count)) { + return; + } + + float fw, fh; + float x, y; + + const float invRealTexWidth = 1.0f / gstate_c.curTextureWidth; + const float invRealTexHeight = 1.0f / gstate_c.curTextureHeight; + + // Preview positions on the framebuffer if (which & 1) { primaryWindow->Begin(); primaryWindow->GetContentSize(x, y, fw, fh); @@ -451,6 +284,7 @@ void CGEDebugger::UpdatePrimPreview(u32 op, int which) { primaryWindow->End(); } + // Preview UVs on the texture if (which & 2) { secondWindow->Begin(); secondWindow->GetContentSize(x, y, fw, fh); @@ -533,7 +367,7 @@ void CGEDebugger::HandleRedraw(int which) { } u32 op = PrimPreviewOp(); - if (op) { + if (op && !showClut_) { UpdatePrimPreview(op, which); } } From 4020fd8ec56e4e11721997a0efec5208e61d0057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Dec 2024 13:38:05 +0100 Subject: [PATCH 27/58] Move the last of the vertex preview code to State.cpp --- GPU/Debugger/State.cpp | 119 ++++++++++++++++++++++++++- GPU/Debugger/State.h | 4 +- Windows/GEDebugger/VertexPreview.cpp | 113 ------------------------- 3 files changed, 117 insertions(+), 119 deletions(-) diff --git a/GPU/Debugger/State.cpp b/GPU/Debugger/State.cpp index b242c515f6ba..03361880bbf6 100644 --- a/GPU/Debugger/State.cpp +++ b/GPU/Debugger/State.cpp @@ -668,7 +668,7 @@ static void RotateUVThrough(GPUDebugVertex v[4]) { SwapUVs(v[1], v[3]); } -void ExpandRectangles(std::vector &vertices, std::vector &indices, int &count, bool throughMode) { +static void ExpandRectangles(std::vector &vertices, std::vector &indices, int &count, bool throughMode) { static std::vector newVerts; static std::vector newInds; @@ -728,7 +728,7 @@ void ExpandRectangles(std::vector &vertices, std::vector &i count *= 3; } -void ExpandBezier(int &count, int op, const std::vector &simpleVerts, const std::vector &indices, std::vector &generatedVerts, std::vector &generatedInds) { +static void ExpandBezier(int &count, int op, const std::vector &simpleVerts, const std::vector &indices, std::vector &generatedVerts, std::vector &generatedInds) { using namespace Spline; int count_u = (op >> 0) & 0xFF; @@ -777,7 +777,7 @@ void ExpandBezier(int &count, int op, const std::vector &simpleVer delete[] cpoints.col; } -void ExpandSpline(int &count, int op, const std::vector &simpleVerts, const std::vector &indices, std::vector &generatedVerts, std::vector &generatedInds) { +static void ExpandSpline(int &count, int op, const std::vector &simpleVerts, const std::vector &indices, std::vector &generatedVerts, std::vector &generatedInds) { using namespace Spline; int count_u = (op >> 0) & 0xFF; @@ -828,3 +828,116 @@ void ExpandSpline(int &count, int op, const std::vector &simpleVer FreeAlignedMemory(cpoints.tex); FreeAlignedMemory(cpoints.col); } + +bool GetPrimPreview(u32 op, int which, GEPrimitiveType &prim, std::vector &vertices, std::vector &indices, int &count) { + u32 prim_type = GE_PRIM_INVALID; + int count_u = 0; + int count_v = 0; + + const u32 cmd = op >> 24; + if (cmd == GE_CMD_PRIM) { + prim_type = (op >> 16) & 0x7; + count = op & 0xFFFF; + } else { + const GEPrimitiveType primLookup[] = { GE_PRIM_TRIANGLES, GE_PRIM_LINES, GE_PRIM_POINTS, GE_PRIM_POINTS }; + if (gstate.getPatchPrimitiveType() < ARRAY_SIZE(primLookup)) + prim_type = primLookup[gstate.getPatchPrimitiveType()]; + count_u = (op & 0x00FF) >> 0; + count_v = (op & 0xFF00) >> 8; + count = count_u * count_v; + } + + if (prim_type >= 7) { + ERROR_LOG(Log::G3D, "Unsupported prim type: %x", op); + return false; + } + if (!gpuDebug) { + ERROR_LOG(Log::G3D, "Invalid debugging environment, shutting down?"); + return false; + } + if (count == 0 || which == 0) { + return false; + } + + prim = static_cast(prim_type); + + if (!gpuDebug->GetCurrentSimpleVertices(count, vertices, indices)) { + ERROR_LOG(Log::G3D, "Vertex preview not yet supported"); + return false; + } + + if (cmd != GE_CMD_PRIM) { + static std::vector generatedVerts; + static std::vector generatedInds; + + static std::vector simpleVerts; + simpleVerts.resize(vertices.size()); + for (size_t i = 0; i < vertices.size(); ++i) { + // For now, let's just copy back so we can use TessellateBezierPatch/TessellateSplinePatch... + simpleVerts[i].uv[0] = vertices[i].u; + simpleVerts[i].uv[1] = vertices[i].v; + simpleVerts[i].pos = Vec3Packedf(vertices[i].x, vertices[i].y, vertices[i].z); + } + + if (cmd == GE_CMD_BEZIER) { + ExpandBezier(count, op, simpleVerts, indices, generatedVerts, generatedInds); + } else if (cmd == GE_CMD_SPLINE) { + ExpandSpline(count, op, simpleVerts, indices, generatedVerts, generatedInds); + } + + vertices.resize(generatedVerts.size()); + for (size_t i = 0; i < vertices.size(); ++i) { + vertices[i].u = generatedVerts[i].uv[0]; + vertices[i].v = generatedVerts[i].uv[1]; + vertices[i].x = generatedVerts[i].pos.x; + vertices[i].y = generatedVerts[i].pos.y; + vertices[i].z = generatedVerts[i].pos.z; + } + indices = generatedInds; + } + + if (prim == GE_PRIM_RECTANGLES) { + ExpandRectangles(vertices, indices, count, gpuDebug->GetGState().isModeThrough()); + } + + // TODO: Probably there's a better way and place to do this. + u16 minIndex = 0; + u16 maxIndex = count - 1; + if (!indices.empty()) { + _dbg_assert_(count <= indices.size()); + minIndex = 0xFFFF; + maxIndex = 0; + for (int i = 0; i < count; ++i) { + if (minIndex > indices[i]) { + minIndex = indices[i]; + } + if (maxIndex < indices[i]) { + maxIndex = indices[i]; + } + } + } + + auto wrapCoord = [](float &coord) { + if (coord < 0.0f) { + coord += ceilf(-coord); + } + if (coord > 1.0f) { + coord -= floorf(coord); + } + }; + + const float invTexWidth = 1.0f / gpuDebug->GetGState().getTextureWidth(0); + const float invTexHeight = 1.0f / gpuDebug->GetGState().getTextureHeight(0); + bool clampS = gpuDebug->GetGState().isTexCoordClampedS(); + bool clampT = gpuDebug->GetGState().isTexCoordClampedT(); + for (u16 i = minIndex; i <= maxIndex; ++i) { + vertices[i].u *= invTexWidth; + vertices[i].v *= invTexHeight; + if (!clampS) + wrapCoord(vertices[i].u); + if (!clampT) + wrapCoord(vertices[i].v); + } + + return true; +} diff --git a/GPU/Debugger/State.h b/GPU/Debugger/State.h index 715610d3e0c9..1c311f5c1f70 100644 --- a/GPU/Debugger/State.h +++ b/GPU/Debugger/State.h @@ -44,6 +44,4 @@ void FormatVertColRaw(VertexDecoder *decoder, char *dest, size_t destSize, int r // These are utilities used by the debugger vertex preview. // Later I hope to re-use more of the real logic. -void ExpandBezier(int &count, int op, const std::vector &simpleVerts, const std::vector &indices, std::vector &generatedVerts, std::vector &generatedInds); -void ExpandSpline(int &count, int op, const std::vector &simpleVerts, const std::vector &indices, std::vector &generatedVerts, std::vector &generatedInds); -void ExpandRectangles(std::vector &vertices, std::vector &indices, int &count, bool throughMode); +bool GetPrimPreview(u32 op, int which, GEPrimitiveType &prim, std::vector &vertices, std::vector &indices, int &count); diff --git a/Windows/GEDebugger/VertexPreview.cpp b/Windows/GEDebugger/VertexPreview.cpp index a0715bc5f0b1..486c989321cf 100644 --- a/Windows/GEDebugger/VertexPreview.cpp +++ b/Windows/GEDebugger/VertexPreview.cpp @@ -86,119 +86,6 @@ u32 CGEDebugger::PrimPreviewOp() { return 0; } -bool GetPrimPreview(u32 op, int which, GEPrimitiveType &prim, std::vector &vertices, std::vector &indices, int &count) { - u32 prim_type = GE_PRIM_INVALID; - int count_u = 0; - int count_v = 0; - - const u32 cmd = op >> 24; - if (cmd == GE_CMD_PRIM) { - prim_type = (op >> 16) & 0x7; - count = op & 0xFFFF; - } else { - const GEPrimitiveType primLookup[] = { GE_PRIM_TRIANGLES, GE_PRIM_LINES, GE_PRIM_POINTS, GE_PRIM_POINTS }; - if (gstate.getPatchPrimitiveType() < ARRAY_SIZE(primLookup)) - prim_type = primLookup[gstate.getPatchPrimitiveType()]; - count_u = (op & 0x00FF) >> 0; - count_v = (op & 0xFF00) >> 8; - count = count_u * count_v; - } - - if (prim_type >= 7) { - ERROR_LOG(Log::G3D, "Unsupported prim type: %x", op); - return false; - } - if (!gpuDebug) { - ERROR_LOG(Log::G3D, "Invalid debugging environment, shutting down?"); - return false; - } - if (count == 0 || which == 0) { - return false; - } - - prim = static_cast(prim_type); - - if (!gpuDebug->GetCurrentSimpleVertices(count, vertices, indices)) { - ERROR_LOG(Log::G3D, "Vertex preview not yet supported"); - return false; - } - - if (cmd != GE_CMD_PRIM) { - static std::vector generatedVerts; - static std::vector generatedInds; - - static std::vector simpleVerts; - simpleVerts.resize(vertices.size()); - for (size_t i = 0; i < vertices.size(); ++i) { - // For now, let's just copy back so we can use TessellateBezierPatch/TessellateSplinePatch... - simpleVerts[i].uv[0] = vertices[i].u; - simpleVerts[i].uv[1] = vertices[i].v; - simpleVerts[i].pos = Vec3Packedf(vertices[i].x, vertices[i].y, vertices[i].z); - } - - if (cmd == GE_CMD_BEZIER) { - ExpandBezier(count, op, simpleVerts, indices, generatedVerts, generatedInds); - } else if (cmd == GE_CMD_SPLINE) { - ExpandSpline(count, op, simpleVerts, indices, generatedVerts, generatedInds); - } - - vertices.resize(generatedVerts.size()); - for (size_t i = 0; i < vertices.size(); ++i) { - vertices[i].u = generatedVerts[i].uv[0]; - vertices[i].v = generatedVerts[i].uv[1]; - vertices[i].x = generatedVerts[i].pos.x; - vertices[i].y = generatedVerts[i].pos.y; - vertices[i].z = generatedVerts[i].pos.z; - } - indices = generatedInds; - } - - if (prim == GE_PRIM_RECTANGLES) { - ExpandRectangles(vertices, indices, count, gpuDebug->GetGState().isModeThrough()); - } - - // TODO: Probably there's a better way and place to do this. - u16 minIndex = 0; - u16 maxIndex = count - 1; - if (!indices.empty()) { - _dbg_assert_(count <= indices.size()); - minIndex = 0xFFFF; - maxIndex = 0; - for (int i = 0; i < count; ++i) { - if (minIndex > indices[i]) { - minIndex = indices[i]; - } - if (maxIndex < indices[i]) { - maxIndex = indices[i]; - } - } - } - - auto wrapCoord = [](float &coord) { - if (coord < 0.0f) { - coord += ceilf(-coord); - } - if (coord > 1.0f) { - coord -= floorf(coord); - } - }; - - const float invTexWidth = 1.0f / gpuDebug->GetGState().getTextureWidth(0); - const float invTexHeight = 1.0f / gpuDebug->GetGState().getTextureHeight(0); - bool clampS = gpuDebug->GetGState().isTexCoordClampedS(); - bool clampT = gpuDebug->GetGState().isTexCoordClampedT(); - for (u16 i = minIndex; i <= maxIndex; ++i) { - vertices[i].u *= invTexWidth; - vertices[i].v *= invTexHeight; - if (!clampS) - wrapCoord(vertices[i].u); - if (!clampT) - wrapCoord(vertices[i].v); - } - - return true; -} - void CGEDebugger::UpdatePrimPreview(u32 op, int which) { which &= previewsEnabled_; From 8d1fbe952229534d751eeda67d7c520f8cbf054d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 12 Dec 2024 19:36:55 +0100 Subject: [PATCH 28/58] Move more utility functions from the Win32 GE debugger to the common code --- GPU/Debugger/State.cpp | 145 ++++++++++++++++++++++++++++++ GPU/Debugger/State.h | 3 + UI/ImDebugger/ImGe.cpp | 18 +++- Windows/GEDebugger/GEDebugger.cpp | 143 +---------------------------- Windows/GEDebugger/GEDebugger.h | 2 - 5 files changed, 165 insertions(+), 146 deletions(-) diff --git a/GPU/Debugger/State.cpp b/GPU/Debugger/State.cpp index 03361880bbf6..54d5be6062ae 100644 --- a/GPU/Debugger/State.cpp +++ b/GPU/Debugger/State.cpp @@ -1,8 +1,10 @@ #include #include "Common/Common.h" #include "Common/StringUtils.h" +#include "Common/Data/Convert/ColorConv.h" #include "GPU/Debugger/State.h" #include "GPU/GPU.h" +#include "GPU/Common/GPUStateUtils.h" #include "GPU/Common/GPUDebugInterface.h" #include "GPU/GeDisasm.h" #include "GPU/Common/VertexDecoderCommon.h" @@ -941,3 +943,146 @@ bool GetPrimPreview(u32 op, int which, GEPrimitiveType &prim, std::vector> 24) & 0xFF, ((pix >> 24) & 0xFF) * (1.0f / 255.0f)); + break; + + case GPU_DBG_FORMAT_FLOAT: + { + float pixf = *(float *)&pix; + DepthScaleFactors depthScale = GetDepthScaleFactors(gstate_c.UseFlags()); + snprintf(desc, 256, "%d,%d: %f / %f", x, y, pixf, depthScale.DecodeToU16(pixf)); + break; + } + + case GPU_DBG_FORMAT_FLOAT_DIV_256: + { + double z = *(float *)&pix; + int z24 = (int)(z * 16777215.0); + + DepthScaleFactors factors = GetDepthScaleFactors(gstate_c.UseFlags()); + // TODO: Use GetDepthScaleFactors here too, verify it's the same. + int z16 = z24 - 0x800000 + 0x8000; + + int z16_2 = factors.DecodeToU16(z); + + snprintf(desc, 256, "%d,%d: %d / %f", x, y, z16, (z - 0.5 + (1.0 / 512.0)) * 256.0); + } + break; + + default: + snprintf(desc, 256, "Unexpected format"); + } +} + +void DescribePixelRGBA(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]) { + u32 r = -1, g = -1, b = -1, a = -1; + + switch (fmt) { + case GPU_DBG_FORMAT_565: + r = Convert5To8((pix >> 0) & 0x1F); + g = Convert6To8((pix >> 5) & 0x3F); + b = Convert5To8((pix >> 11) & 0x1F); + break; + case GPU_DBG_FORMAT_565_REV: + b = Convert5To8((pix >> 0) & 0x1F); + g = Convert6To8((pix >> 5) & 0x3F); + r = Convert5To8((pix >> 11) & 0x1F); + break; + case GPU_DBG_FORMAT_5551: + r = Convert5To8((pix >> 0) & 0x1F); + g = Convert5To8((pix >> 5) & 0x1F); + b = Convert5To8((pix >> 10) & 0x1F); + a = (pix >> 15) & 1 ? 255 : 0; + break; + case GPU_DBG_FORMAT_5551_REV: + a = pix & 1 ? 255 : 0; + b = Convert5To8((pix >> 1) & 0x1F); + g = Convert5To8((pix >> 6) & 0x1F); + r = Convert5To8((pix >> 11) & 0x1F); + break; + case GPU_DBG_FORMAT_5551_BGRA: + b = Convert5To8((pix >> 0) & 0x1F); + g = Convert5To8((pix >> 5) & 0x1F); + r = Convert5To8((pix >> 10) & 0x1F); + a = (pix >> 15) & 1 ? 255 : 0; + break; + case GPU_DBG_FORMAT_4444: + r = Convert4To8((pix >> 0) & 0x0F); + g = Convert4To8((pix >> 4) & 0x0F); + b = Convert4To8((pix >> 8) & 0x0F); + a = Convert4To8((pix >> 12) & 0x0F); + break; + case GPU_DBG_FORMAT_4444_REV: + a = Convert4To8((pix >> 0) & 0x0F); + b = Convert4To8((pix >> 4) & 0x0F); + g = Convert4To8((pix >> 8) & 0x0F); + r = Convert4To8((pix >> 12) & 0x0F); + break; + case GPU_DBG_FORMAT_4444_BGRA: + b = Convert4To8((pix >> 0) & 0x0F); + g = Convert4To8((pix >> 4) & 0x0F); + r = Convert4To8((pix >> 8) & 0x0F); + a = Convert4To8((pix >> 12) & 0x0F); + break; + case GPU_DBG_FORMAT_8888: + r = (pix >> 0) & 0xFF; + g = (pix >> 8) & 0xFF; + b = (pix >> 16) & 0xFF; + a = (pix >> 24) & 0xFF; + break; + case GPU_DBG_FORMAT_8888_BGRA: + b = (pix >> 0) & 0xFF; + g = (pix >> 8) & 0xFF; + r = (pix >> 16) & 0xFF; + a = (pix >> 24) & 0xFF; + break; + + default: + snprintf(desc, 256, "Unexpected format"); + return; + } + + snprintf(desc, 256, "%d,%d: r=%d, g=%d, b=%d, a=%d", x, y, r, g, b, a); +} diff --git a/GPU/Debugger/State.h b/GPU/Debugger/State.h index 1c311f5c1f70..5ac998a44332 100644 --- a/GPU/Debugger/State.h +++ b/GPU/Debugger/State.h @@ -8,6 +8,7 @@ #include "GPU/Debugger/GECommandTable.h" #include "GPU/Common/SplineCommon.h" +#include "GPU/Common/GPUDebugInterface.h" enum VertexListCols { VERTEXLIST_COL_X, @@ -45,3 +46,5 @@ void FormatVertColRaw(VertexDecoder *decoder, char *dest, size_t destSize, int r // Later I hope to re-use more of the real logic. bool GetPrimPreview(u32 op, int which, GEPrimitiveType &prim, std::vector &vertices, std::vector &indices, int &count); +void DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]); +void DescribePixelRGBA(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]); diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index 52fae575671c..96a5ac0bc461 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -243,6 +243,17 @@ void ImGeDisasmView::Draw(GPUDebugInterface *gpuDebug) { } } +static const char *DLStateToString(DisplayListState state) { + switch (state) { + case PSP_GE_DL_STATE_NONE: return "None"; + case PSP_GE_DL_STATE_QUEUED: return "Queued"; + case PSP_GE_DL_STATE_RUNNING: return "Running"; + case PSP_GE_DL_STATE_COMPLETED: return "Completed"; + case PSP_GE_DL_STATE_PAUSED: return "Paused"; + default: return "N/A (bad)"; + } +} + void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterface *gpuDebug) { ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); if (!ImGui::Begin(Title(), &cfg.geDebuggerOpen)) { @@ -333,14 +344,17 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa const auto &list = gpuDebug->GetDisplayList(index); char title[64]; snprintf(title, sizeof(title), "List %d", list.id); - if (ImGui::CollapsingHeader(title, ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader(title)) { + ImGui::Text("State: %s", DLStateToString(list.state)); ImGui::TextUnformatted("PC:"); ImGui::SameLine(); ImClickableAddress(list.pc, control, ImCmd::SHOW_IN_GE_DISASM); ImGui::Text("StartPC:"); ImGui::SameLine(); ImClickableAddress(list.startpc, control, ImCmd::SHOW_IN_GE_DISASM); - ImGui::Text("Pending interrupt: %d", (int)list.pendingInterrupt); + if (list.pendingInterrupt) { + ImGui::TextUnformatted("(Pending interrupt)"); + } ImGui::Text("Stack depth: %d", (int)list.stackptr); ImGui::Text("BBOX result: %d", (int)list.bboxResult); } diff --git a/Windows/GEDebugger/GEDebugger.cpp b/Windows/GEDebugger/GEDebugger.cpp index 19bf460eff4b..2878ad186734 100644 --- a/Windows/GEDebugger/GEDebugger.cpp +++ b/Windows/GEDebugger/GEDebugger.cpp @@ -53,6 +53,7 @@ #include "GPU/Debugger/Breakpoints.h" #include "GPU/Debugger/Debugger.h" #include "GPU/Debugger/Record.h" +#include "GPU/Debugger/State.h" #include "GPU/Debugger/Stepping.h" using namespace GPUBreakpoints; @@ -829,148 +830,6 @@ void CGEDebugger::SecondPreviewHover(int x, int y) { SetDlgItemText(m_hDlg, IDC_GEDBG_TEXADDR, w_desc); } -void CGEDebugger::DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]) { - switch (fmt) { - case GPU_DBG_FORMAT_565: - case GPU_DBG_FORMAT_565_REV: - case GPU_DBG_FORMAT_5551: - case GPU_DBG_FORMAT_5551_REV: - case GPU_DBG_FORMAT_5551_BGRA: - case GPU_DBG_FORMAT_4444: - case GPU_DBG_FORMAT_4444_REV: - case GPU_DBG_FORMAT_4444_BGRA: - case GPU_DBG_FORMAT_8888: - case GPU_DBG_FORMAT_8888_BGRA: - DescribePixelRGBA(pix, fmt, x, y, desc); - break; - - case GPU_DBG_FORMAT_16BIT: - snprintf(desc, 256, "%d,%d: %d / %f", x, y, pix, pix * (1.0f / 65535.0f)); - break; - - case GPU_DBG_FORMAT_8BIT: - snprintf(desc, 256, "%d,%d: %d / %f", x, y, pix, pix * (1.0f / 255.0f)); - break; - - case GPU_DBG_FORMAT_24BIT_8X: - { - DepthScaleFactors depthScale = GetDepthScaleFactors(gstate_c.UseFlags()); - // These are only ever going to be depth values, so let's also show scaled to 16 bit. - snprintf(desc, 256, "%d,%d: %d / %f / %f", x, y, pix & 0x00FFFFFF, (pix & 0x00FFFFFF) * (1.0f / 16777215.0f), depthScale.DecodeToU16((pix & 0x00FFFFFF) * (1.0f / 16777215.0f))); - break; - } - - case GPU_DBG_FORMAT_24BIT_8X_DIV_256: - { - // These are only ever going to be depth values, so let's also show scaled to 16 bit. - int z24 = pix & 0x00FFFFFF; - int z16 = z24 - 0x800000 + 0x8000; - snprintf(desc, 256, "%d,%d: %d / %f", x, y, z16, z16 * (1.0f / 65535.0f)); - } - break; - - case GPU_DBG_FORMAT_24X_8BIT: - snprintf(desc, 256, "%d,%d: %d / %f", x, y, (pix >> 24) & 0xFF, ((pix >> 24) & 0xFF) * (1.0f / 255.0f)); - break; - - case GPU_DBG_FORMAT_FLOAT: { - float pixf = *(float *)&pix; - DepthScaleFactors depthScale = GetDepthScaleFactors(gstate_c.UseFlags()); - snprintf(desc, 256, "%d,%d: %f / %f", x, y, pixf, depthScale.DecodeToU16(pixf)); - break; - } - - case GPU_DBG_FORMAT_FLOAT_DIV_256: - { - double z = *(float *)&pix; - int z24 = (int)(z * 16777215.0); - - DepthScaleFactors factors = GetDepthScaleFactors(gstate_c.UseFlags()); - // TODO: Use GetDepthScaleFactors here too, verify it's the same. - int z16 = z24 - 0x800000 + 0x8000; - - int z16_2 = factors.DecodeToU16(z); - - snprintf(desc, 256, "%d,%d: %d / %f", x, y, z16, (z - 0.5 + (1.0 / 512.0)) * 256.0); - } - break; - - default: - snprintf(desc, 256, "Unexpected format"); - } -} - -void CGEDebugger::DescribePixelRGBA(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]) { - u32 r = -1, g = -1, b = -1, a = -1; - - switch (fmt) { - case GPU_DBG_FORMAT_565: - r = Convert5To8((pix >> 0) & 0x1F); - g = Convert6To8((pix >> 5) & 0x3F); - b = Convert5To8((pix >> 11) & 0x1F); - break; - case GPU_DBG_FORMAT_565_REV: - b = Convert5To8((pix >> 0) & 0x1F); - g = Convert6To8((pix >> 5) & 0x3F); - r = Convert5To8((pix >> 11) & 0x1F); - break; - case GPU_DBG_FORMAT_5551: - r = Convert5To8((pix >> 0) & 0x1F); - g = Convert5To8((pix >> 5) & 0x1F); - b = Convert5To8((pix >> 10) & 0x1F); - a = (pix >> 15) & 1 ? 255 : 0; - break; - case GPU_DBG_FORMAT_5551_REV: - a = pix & 1 ? 255 : 0; - b = Convert5To8((pix >> 1) & 0x1F); - g = Convert5To8((pix >> 6) & 0x1F); - r = Convert5To8((pix >> 11) & 0x1F); - break; - case GPU_DBG_FORMAT_5551_BGRA: - b = Convert5To8((pix >> 0) & 0x1F); - g = Convert5To8((pix >> 5) & 0x1F); - r = Convert5To8((pix >> 10) & 0x1F); - a = (pix >> 15) & 1 ? 255 : 0; - break; - case GPU_DBG_FORMAT_4444: - r = Convert4To8((pix >> 0) & 0x0F); - g = Convert4To8((pix >> 4) & 0x0F); - b = Convert4To8((pix >> 8) & 0x0F); - a = Convert4To8((pix >> 12) & 0x0F); - break; - case GPU_DBG_FORMAT_4444_REV: - a = Convert4To8((pix >> 0) & 0x0F); - b = Convert4To8((pix >> 4) & 0x0F); - g = Convert4To8((pix >> 8) & 0x0F); - r = Convert4To8((pix >> 12) & 0x0F); - break; - case GPU_DBG_FORMAT_4444_BGRA: - b = Convert4To8((pix >> 0) & 0x0F); - g = Convert4To8((pix >> 4) & 0x0F); - r = Convert4To8((pix >> 8) & 0x0F); - a = Convert4To8((pix >> 12) & 0x0F); - break; - case GPU_DBG_FORMAT_8888: - r = (pix >> 0) & 0xFF; - g = (pix >> 8) & 0xFF; - b = (pix >> 16) & 0xFF; - a = (pix >> 24) & 0xFF; - break; - case GPU_DBG_FORMAT_8888_BGRA: - b = (pix >> 0) & 0xFF; - g = (pix >> 8) & 0xFF; - r = (pix >> 16) & 0xFF; - a = (pix >> 24) & 0xFF; - break; - - default: - snprintf(desc, 256, "Unexpected format"); - return; - } - - snprintf(desc, 256, "%d,%d: r=%d, g=%d, b=%d, a=%d", x, y, r, g, b, a); -} - void CGEDebugger::UpdateTextureLevel(int level) { GPUgstate state{}; if (gpuDebug != nullptr) { diff --git a/Windows/GEDebugger/GEDebugger.h b/Windows/GEDebugger/GEDebugger.h index bb8b8e08ac4a..61d90107ef82 100644 --- a/Windows/GEDebugger/GEDebugger.h +++ b/Windows/GEDebugger/GEDebugger.h @@ -115,8 +115,6 @@ class CGEDebugger : public Dialog { void SecondPreviewHover(int x, int y); void PreviewExport(const GPUDebugBuffer *buffer); void PreviewToClipboard(const GPUDebugBuffer *buffer, bool saveAlpha); - static void DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]); - static void DescribePixelRGBA(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]); void UpdateMenus(); void UpdateTab(GEDebuggerTab *tab); void AddTab(GEDebuggerTab *tab, GETabPosition mask); From fa3321ca0c824b46e88c1be31e083172231ab0f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 12 Dec 2024 22:50:51 +0100 Subject: [PATCH 29/58] ImDebugger: Switch "Dear Imgui" to the Docking branch, enable the feature --- GPU/Common/TextureCacheCommon.cpp | 3 +- UI/ImDebugger/ImDebugger.cpp | 17 +- UI/ImDebugger/ImGe.cpp | 2 +- ext/imgui/imgui.cpp | 8593 +++++++++++++++++++++++++---- ext/imgui/imgui.h | 1072 +++- ext/imgui/imgui_demo.cpp | 2672 +++++++-- ext/imgui/imgui_draw.cpp | 566 +- ext/imgui/imgui_impl_platform.cpp | 1 + ext/imgui/imgui_internal.h | 1149 ++-- ext/imgui/imgui_tables.cpp | 128 +- ext/imgui/imgui_widgets.cpp | 2577 +++++++-- ext/imgui/imstb_textedit.h | 98 +- 12 files changed, 14004 insertions(+), 2874 deletions(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 956dd2558a14..d10712d11d3b 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -47,6 +47,7 @@ #include "Core/Util/PPGeDraw.h" #include "ext/imgui/imgui.h" +#include "ext/imgui/imgui_internal.h" #include "ext/imgui/imgui_impl_thin3d.h" @@ -3066,7 +3067,7 @@ void TextureCacheCommon::DrawImGuiDebug(uint64_t &selectedTextureId) const { ImVec2 avail = ImGui::GetContentRegionAvail(); auto &style = ImGui::GetStyle(); ImGui::BeginChild("left", ImVec2(140.0f, 0.0f), ImGuiChildFlags_ResizeX); - float window_visible_x2 = ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x; + float window_visible_x2 = ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x; // Global texture stats int replacementStateCounts[(int)ReplacementState::COUNT]{}; diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 5d206b06452b..b4b4939c5def 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -1161,7 +1161,12 @@ void ImMemWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl & // Toolbars - if (ImGui::InputScalar("Go to addr: ", ImGuiDataType_U32, &gotoAddr_, NULL, NULL, "%08X", ImGuiInputTextFlags_EnterReturnsTrue)) { + ImGui::InputScalar("Go to addr: ", ImGuiDataType_U32, &gotoAddr_, NULL, NULL, "%08X"); + if (ImGui::IsItemDeactivatedAfterEdit()) { + memView_.gotoAddr(gotoAddr_); + } + ImGui::SameLine(); + if (ImGui::SmallButton("Go")) { memView_.gotoAddr(gotoAddr_); } @@ -1179,16 +1184,16 @@ void ImMemWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl & if (ImGui::Selectable("VRAM")) { GotoAddr(0x08800000); } - ImGui::EndChild(); } + ImGui::EndChild(); + ImGui::SameLine(); - ImGui::BeginGroup(); if (ImGui::BeginChild("memview", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()))) { memView_.Draw(ImGui::GetWindowDrawList()); - ImGui::EndChild(); } + ImGui::EndChild(); + ImGui::TextUnformatted(memView_.StatusMessage().c_str()); - ImGui::EndGroup(); ImGui::End(); } @@ -1331,7 +1336,7 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImContro } ImGui::SetNextItemWidth(100); - if (ImGui::InputScalar("Go to addr: ", ImGuiDataType_U32, &gotoAddr_, NULL, NULL, "%08X", ImGuiInputTextFlags_EnterReturnsTrue)) { + if (ImGui::InputScalar("Go to addr: ", ImGuiDataType_U32, &gotoAddr_, NULL, NULL, "%08X")) { disasmView_.setCurAddress(gotoAddr_); disasmView_.scrollAddressIntoView(); } diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index 96a5ac0bc461..d5e73da10e22 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -338,7 +338,7 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa // First, let's list any active display lists in the left column, on top of the disassembly. - ImGui::BeginChild("left pane", ImVec2(400, 0), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeX); + ImGui::BeginChild("left pane", ImVec2(400, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeX); for (auto index : gpuDebug->GetDisplayListQueue()) { const auto &list = gpuDebug->GetDisplayList(index); diff --git a/ext/imgui/imgui.cpp b/ext/imgui/imgui.cpp index d818c64c017c..c7b84cc6ce6e 100644 --- a/ext/imgui/imgui.cpp +++ b/ext/imgui/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.9 WIP +// dear imgui, v1.91.6 // (main code and documentation) // Help: @@ -10,7 +10,7 @@ // - FAQ ........................ https://dearimgui.com/faq (in repository as docs/FAQ.md) // - Homepage ................... https://github.com/ocornut/imgui // - Releases & changelog ....... https://github.com/ocornut/imgui/releases -// - Gallery .................... https://github.com/ocornut/imgui/issues/7503 (please post your screenshots/video there!) +// - Gallery .................... https://github.com/ocornut/imgui/issues?q=label%3Agallery (please post your screenshots/video there!) // - Wiki ....................... https://github.com/ocornut/imgui/wiki (lots of good stuff there) // - Getting Started https://github.com/ocornut/imgui/wiki/Getting-Started (how to integrate in an existing app by adding ~25 lines of code) // - Third-party Extensions https://github.com/ocornut/imgui/wiki/Useful-Extensions (ImPlot & many more) @@ -39,8 +39,6 @@ // come with any guarantee of forward compatibility. Discussing your changes on the GitHub Issue Tracker may lead you // to a better solution or official support for them. -#undef new - /* Index of this file: @@ -65,7 +63,7 @@ CODE // [SECTION] INCLUDES // [SECTION] FORWARD DECLARATIONS // [SECTION] CONTEXT AND MEMORY ALLOCATORS -// [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO) +// [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO, ImGuiPlatformIO) // [SECTION] MISC HELPERS/UTILITIES (Geometry functions) // [SECTION] MISC HELPERS/UTILITIES (String, Format, Hash functions) // [SECTION] MISC HELPERS/UTILITIES (File functions) @@ -81,7 +79,7 @@ CODE // [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) // [SECTION] ID STACK // [SECTION] INPUTS -// [SECTION] ERROR CHECKING +// [SECTION] ERROR CHECKING, STATE RECOVERY // [SECTION] ITEM SUBMISSION // [SECTION] LAYOUT // [SECTION] SCROLLING @@ -93,6 +91,7 @@ CODE // [SECTION] SETTINGS // [SECTION] LOCALIZATION // [SECTION] VIEWPORTS, PLATFORM WINDOWS +// [SECTION] DOCKING // [SECTION] PLATFORM DEPENDENT HELPERS // [SECTION] METRICS/DEBUGGER WINDOW // [SECTION] DEBUG LOG WINDOW @@ -176,7 +175,6 @@ CODE - Set 'io.BackendFlags |= ImGuiBackendFlags_HasGamepad' + call io.AddKeyEvent/AddKeyAnalogEvent() with ImGuiKey_Gamepad_XXX keys. - For analog values (0.0f to 1.0f), backend is responsible to handling a dead-zone and rescaling inputs accordingly. Backend code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw input range to 0.0..1.0 imgui range, etc.). - - BEFORE 1.87, BACKENDS USED TO WRITE TO io.NavInputs[]. This is now obsolete. Please call io functions instead! - If you need to share inputs between your game and the Dear ImGui interface, the easiest approach is to go all-or-nothing, with a buttons combo to toggle the target. Please reach out if you think the game vs navigation input sharing could be improved. @@ -185,8 +183,8 @@ CODE - Consoles/Tablet/Phone users: Consider using a Synergy 1.x server (on your PC) + run examples/libs/synergy/uSynergy.c (on your console/tablet/phone app) in order to share your PC mouse/keyboard. - See https://github.com/ocornut/imgui/wiki/Useful-Extensions#remoting for other remoting solutions. - - On a TV/console system where readability may be lower or mouse inputs may be awkward, you may want to set the ImGuiConfigFlags_NavEnableSetMousePos flag. - Enabling ImGuiConfigFlags_NavEnableSetMousePos + ImGuiBackendFlags_HasSetMousePos instructs Dear ImGui to move your mouse cursor along with navigation movements. + - On a TV/console system where readability may be lower or mouse inputs may be awkward, you may want to set the io.ConfigNavMoveSetMousePos flag. + Enabling io.ConfigNavMoveSetMousePos + ImGuiBackendFlags_HasSetMousePos instructs Dear ImGui to move your mouse cursor along with navigation movements. When enabled, the NewFrame() function may alter 'io.MousePos' and set 'io.WantSetMousePos' to notify you that it wants the mouse cursor to be moved. When that happens your backend NEEDS to move the OS or underlying mouse cursor on the next frame. Some of the backends in examples/ do that. (If you set the NavEnableSetMousePos flag but don't honor 'io.WantSetMousePos' properly, Dear ImGui will misbehave as it will see your mouse moving back & forth!) @@ -432,6 +430,84 @@ CODE When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. +(Docking/Viewport Branch) + - 2024/XX/XX (1.XXXX) - when multi-viewports are enabled, all positions will be in your natural OS coordinates space. It means that: + - reference to hard-coded positions such as in SetNextWindowPos(ImVec2(0,0)) are probably not what you want anymore. + you may use GetMainViewport()->Pos to offset hard-coded positions, e.g. SetNextWindowPos(GetMainViewport()->Pos) + - likewise io.MousePos and GetMousePos() will use OS coordinates. + If you query mouse positions to interact with non-imgui coordinates you will need to offset them, e.g. subtract GetWindowViewport()->Pos. + + - 2024/11/27 (1.91.6) - changed CRC32 table from CRC32-adler to CRC32c polynomial in order to be compatible with the result of SSE 4.2 instructions. + As a result, old .ini data may be partially lost (docking and tables information particularly). + Because some users have crafted and storing .ini data as a way to workaround limitations of the docking API, we are providing a '#define IMGUI_USE_LEGACY_CRC32_ADLER' compile-time option to keep using old CRC32 tables if you cannot afford invalidating old .ini data. + - 2024/11/06 (1.91.5) - commented/obsoleted out pre-1.87 IO system (equivalent to using IMGUI_DISABLE_OBSOLETE_KEYIO or IMGUI_DISABLE_OBSOLETE_FUNCTIONS before) + - io.KeyMap[] and io.KeysDown[] are removed (obsoleted February 2022). + - io.NavInputs[] and ImGuiNavInput are removed (obsoleted July 2022). + - pre-1.87 backends are not supported: + - backends need to call io.AddKeyEvent(), io.AddMouseEvent() instead of writing to io.KeysDown[], io.MouseDown[] fields. + - backends need to call io.AddKeyAnalogEvent() for gamepad values instead of writing to io.NavInputs[] fields. + - for more reference: + - read 1.87 and 1.88 part of this section or read Changelog for 1.87 and 1.88. + - read https://github.com/ocornut/imgui/issues/4921 + - if you have trouble updating a very old codebase using legacy backend-specific key codes: consider updating to 1.91.4 first, then #define IMGUI_DISABLE_OBSOLETE_KEYIO, then update to latest. + - obsoleted ImGuiKey_COUNT (it is unusually error-prone/misleading since valid keys don't start at 0). probably use ImGuiKey_NamedKey_BEGIN/ImGuiKey_NamedKey_END? + - fonts: removed const qualifiers from most font functions in prevision for upcoming font improvements. + - 2024/10/18 (1.91.4) - renamed ImGuiCol_NavHighlight to ImGuiCol_NavCursor (for consistency with newly exposed and reworked features). Kept inline redirection enum (will obsolete). + - 2024/10/14 (1.91.4) - moved ImGuiConfigFlags_NavEnableSetMousePos to standalone io.ConfigNavMoveSetMousePos bool. + moved ImGuiConfigFlags_NavNoCaptureKeyboard to standalone io.ConfigNavCaptureKeyboard bool (note the inverted value!). + kept legacy names (will obsolete) + code that copies settings once the first time. Dynamically changing the old value won't work. Switch to using the new value! + - 2024/10/10 (1.91.4) - the typedef for ImTextureID now defaults to ImU64 instead of void*. (#1641) + this removes the requirement to redefine it for backends which are e.g. storing descriptor sets or other 64-bits structures when building on 32-bits archs. It therefore simplify various building scripts/helpers. + you may have compile-time issues if you were casting to 'void*' instead of 'ImTextureID' when passing your types to functions taking ImTextureID values, e.g. ImGui::Image(). + in doubt it is almost always better to do an intermediate intptr_t cast, since it allows casting any pointer/integer type without warning: + - May warn: ImGui::Image((void*)MyTextureData, ...); + - May warn: ImGui::Image((void*)(intptr_t)MyTextureData, ...); + - Won't warn: ImGui::Image((ImTextureID)(intptr_t)MyTextureData), ...); + - note that you can always define ImTextureID to be your own high-level structures (with dedicated constructors) if you like. + - 2024/10/03 (1.91.3) - drags: treat v_min==v_max as a valid clamping range when != 0.0f. Zero is a still special value due to legacy reasons, unless using ImGuiSliderFlags_ClampZeroRange. (#7968, #3361, #76) + - drags: extended behavior of ImGuiSliderFlags_AlwaysClamp to include _ClampZeroRange. It considers v_min==v_max==0.0f as a valid clamping range (aka edits not allowed). + although unlikely, it you wish to only clamp on text input but want v_min==v_max==0.0f to mean unclamped drags, you can use _ClampOnInput instead of _AlwaysClamp. (#7968, #3361, #76) + - 2024/09/10 (1.91.2) - internals: using multiple overlayed ButtonBehavior() with same ID will now have io.ConfigDebugHighlightIdConflicts=true feature emit a warning. (#8030) + it was one of the rare case where using same ID is legal. workarounds: (1) use single ButtonBehavior() call with multiple _MouseButton flags, or (2) surround the calls with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag() + - 2024/08/23 (1.91.1) - renamed ImGuiChildFlags_Border to ImGuiChildFlags_Borders for consistency. kept inline redirection flag. + - 2024/08/22 (1.91.1) - moved some functions from ImGuiIO to ImGuiPlatformIO structure: + - io.GetClipboardTextFn -> platform_io.Platform_GetClipboardTextFn + changed 'void* user_data' to 'ImGuiContext* ctx'. Pull your user data from platform_io.ClipboardUserData. + - io.SetClipboardTextFn -> platform_io.Platform_SetClipboardTextFn + same as above line. + - io.PlatformOpenInShellFn -> platform_io.Platform_OpenInShellFn (#7660) + - io.PlatformSetImeDataFn -> platform_io.Platform_SetImeDataFn + - io.PlatformLocaleDecimalPoint -> platform_io.Platform_LocaleDecimalPoint (#7389, #6719, #2278) + - access those via GetPlatformIO() instead of GetIO(). + some were introduced very recently and often automatically setup by core library and backends, so for those we are exceptionally not maintaining a legacy redirection symbol. + - commented the old ImageButton() signature obsoleted in 1.89 (~August 2022). As a reminder: + - old ImageButton() before 1.89 used ImTextureId as item id (created issue with e.g. multiple buttons in same scope, transient texture id values, opaque computation of ID) + - new ImageButton() since 1.89 requires an explicit 'const char* str_id' + - old ImageButton() before 1.89 had frame_padding' override argument. + - new ImageButton() since 1.89 always use style.FramePadding, which you can freely override with PushStyleVar()/PopStyleVar(). + - 2024/07/25 (1.91.0) - obsoleted GetContentRegionMax(), GetWindowContentRegionMin() and GetWindowContentRegionMax(). (see #7838 on GitHub for more info) + you should never need those functions. you can do everything with GetCursorScreenPos() and GetContentRegionAvail() in a more simple way. + - instead of: GetWindowContentRegionMax().x - GetCursorPos().x + - you can use: GetContentRegionAvail().x + - instead of: GetWindowContentRegionMax().x + GetWindowPos().x + - you can use: GetCursorScreenPos().x + GetContentRegionAvail().x // when called from left edge of window + - instead of: GetContentRegionMax() + - you can use: GetContentRegionAvail() + GetCursorScreenPos() - GetWindowPos() // right edge in local coordinates + - instead of: GetWindowContentRegionMax().x - GetWindowContentRegionMin().x + - you can use: GetContentRegionAvail() // when called from left edge of window + - 2024/07/15 (1.91.0) - renamed ImGuiSelectableFlags_DontClosePopups to ImGuiSelectableFlags_NoAutoClosePopups. (#1379, #1468, #2200, #4936, #5216, #7302, #7573) + (internals: also renamed ImGuiItemFlags_SelectableDontClosePopup into ImGuiItemFlags_AutoClosePopups with inverted behaviors) + - 2024/07/15 (1.91.0) - obsoleted PushButtonRepeat()/PopButtonRepeat() in favor of using new PushItemFlag(ImGuiItemFlags_ButtonRepeat, ...)/PopItemFlag(). + - 2024/07/02 (1.91.0) - commented out obsolete ImGuiModFlags (renamed to ImGuiKeyChord in 1.89). (#4921, #456) + - commented out obsolete ImGuiModFlags_XXX values (renamed to ImGuiMod_XXX in 1.89). (#4921, #456) + - ImGuiModFlags_Ctrl -> ImGuiMod_Ctrl, ImGuiModFlags_Shift -> ImGuiMod_Shift etc. + - 2024/07/02 (1.91.0) - IO, IME: renamed platform IME hook and added explicit context for consistency and future-proofness. + - old: io.SetPlatformImeDataFn(ImGuiViewport* viewport, ImGuiPlatformImeData* data); + - new: io.PlatformSetImeDataFn(ImGuiContext* ctx, ImGuiViewport* viewport, ImGuiPlatformImeData* data); + - 2024/06/21 (1.90.9) - BeginChild: added ImGuiChildFlags_NavFlattened as a replacement for the window flag ImGuiWindowFlags_NavFlattened: the feature only ever made sense for BeginChild() anyhow. + - old: BeginChild("Name", size, 0, ImGuiWindowFlags_NavFlattened); + - new: BeginChild("Name", size, ImGuiChildFlags_NavFlattened, 0); + - 2024/06/21 (1.90.9) - io: ClearInputKeys() (first exposed in 1.89.8) doesn't clear mouse data, newly added ClearInputMouse() does. + - 2024/06/20 (1.90.9) - renamed ImGuiDragDropFlags_SourceAutoExpirePayload to ImGuiDragDropFlags_PayloadAutoExpire. + - 2024/06/18 (1.90.9) - style: renamed ImGuiCol_TabActive -> ImGuiCol_TabSelected, ImGuiCol_TabUnfocused -> ImGuiCol_TabDimmed, ImGuiCol_TabUnfocusedActive -> ImGuiCol_TabDimmedSelected. - 2024/06/10 (1.90.9) - removed old nested structure: renaming ImGuiStorage::ImGuiStoragePair type to ImGuiStoragePair (simpler for many languages). - 2024/06/06 (1.90.8) - reordered ImGuiInputTextFlags values. This should not be breaking unless you are using generated headers that have values not matching the main library. - 2024/06/06 (1.90.8) - removed 'ImGuiButtonFlags_MouseButtonDefault_ = ImGuiButtonFlags_MouseButtonLeft', was mostly unused and misleading. @@ -452,6 +528,9 @@ CODE - new: IsMouseClicked(ImGuiMouseButton button, ImGuiInputFlags flags, ImGuiID owner_id = 0); for various reasons those changes makes sense. They are being made because making some of those API public. only past users of imgui_internal.h with the extra parameters will be affected. Added asserts for valid flags in various functions to detect _some_ misuses, BUT NOT ALL. + - 2024/05/21 (1.90.7) - docking: changed signature of DockSpaceOverViewport() to add explicit dockspace id if desired. pass 0 to use old behavior. (#7611) + - old: DockSpaceOverViewport(const ImGuiViewport* viewport = NULL, ImGuiDockNodeFlags flags = 0, ...); + - new: DockSpaceOverViewport(ImGuiID dockspace_id = 0, const ImGuiViewport* viewport = NULL, ImGuiDockNodeFlags flags = 0, ...); - 2024/05/16 (1.90.7) - inputs: on macOS X, Cmd and Ctrl keys are now automatically swapped by io.AddKeyEvent() as this naturally align with how macOS X uses those keys. - it shouldn't really affect you unless you had custom shortcut swapping in place for macOS X apps. - removed ImGuiMod_Shortcut which was previously dynamically remapping to Ctrl or Cmd/Super. It is now unnecessary to specific cross-platform idiomatic shortcuts. (#2343, #4084, #5923, #456) @@ -478,6 +557,7 @@ CODE - new: BeginChild("Name", size, ImGuiChildFlags_Border) - old: BeginChild("Name", size, false) - new: BeginChild("Name", size) or BeginChild("Name", 0) or BeginChild("Name", size, ImGuiChildFlags_None) + **AMEND FROM THE FUTURE: from 1.91.1, 'ImGuiChildFlags_Border' is called 'ImGuiChildFlags_Borders'** - 2023/11/02 (1.90.0) - BeginChild: added child-flag ImGuiChildFlags_AlwaysUseWindowPadding as a replacement for the window-flag ImGuiWindowFlags_AlwaysUseWindowPadding: the feature only ever made sense for BeginChild() anyhow. - old: BeginChild("Name", size, 0, ImGuiWindowFlags_AlwaysUseWindowPadding); - new: BeginChild("Name", size, ImGuiChildFlags_AlwaysUseWindowPadding, 0); @@ -1011,7 +1091,7 @@ CODE #endif // [Windows] OS specific includes (optional) -#if defined(_WIN32) && defined(IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS) && defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) && defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) +#if defined(_WIN32) && defined(IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS) && defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) && defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) && defined(IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) #define IMGUI_DISABLE_WIN32_FUNCTIONS #endif #if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) @@ -1030,6 +1110,7 @@ CODE // The UWP and GDK Win32 API subsets don't support clipboard nor IME functions #define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS #define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS +#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS #endif #endif @@ -1068,21 +1149,22 @@ CODE #pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type #elif defined(__GNUC__) // We disable -Wpragmas because GCC doesn't provide a has_warning equivalent and some forks/patches may not follow the warning/version association. -#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind -#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used -#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size -#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*' -#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function -#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value -#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked -#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false -#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size +#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*' +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #endif // Debug options -#define IMGUI_DEBUG_NAV_SCORING 0 // Display navigation scoring preview when hovering items. Display last moving direction matches when holding CTRL +#define IMGUI_DEBUG_NAV_SCORING 0 // Display navigation scoring preview when hovering items. Hold CTRL to display for all candidates. CTRL+Arrow to change last direction. #define IMGUI_DEBUG_NAV_RECTS 0 // Display the reference navigation rectangle for each window // When using CTRL+TAB (or Gamepad Square+L/R) we delay the visual a little in order to reduce visual noise doing a fast switch. @@ -1097,7 +1179,12 @@ static const float WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER = 0.04f; // Reduc static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER = 0.70f; // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time, unless mouse moved. // Tooltip offset -static const ImVec2 TOOLTIP_DEFAULT_OFFSET = ImVec2(16, 10); // Multiplied by g.Style.MouseCursorScale +static const ImVec2 TOOLTIP_DEFAULT_OFFSET_MOUSE = ImVec2(16, 10); // Multiplied by g.Style.MouseCursorScale +static const ImVec2 TOOLTIP_DEFAULT_OFFSET_TOUCH = ImVec2(0, -20); // Multiplied by g.Style.MouseCursorScale +static const ImVec2 TOOLTIP_DEFAULT_PIVOT_TOUCH = ImVec2(0.5f, 1.0f); // Multiplied by g.Style.MouseCursorScale + +// Docking +static const float DOCKING_TRANSPARENT_PAYLOAD_ALPHA = 0.50f; // For use with io.ConfigDockingTransparentPayload. Apply to Viewport _or_ WindowBg in host viewport. //------------------------------------------------------------------------- // [SECTION] FORWARD DECLARATIONS @@ -1116,10 +1203,11 @@ static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSetti static void WindowSettingsHandler_ApplyAll(ImGuiContext*, ImGuiSettingsHandler*); static void WindowSettingsHandler_WriteAll(ImGuiContext*, ImGuiSettingsHandler*, ImGuiTextBuffer* buf); -// Platform Dependents default implementation for IO functions -static const char* GetClipboardTextFn_DefaultImpl(void* user_data_ctx); -static void SetClipboardTextFn_DefaultImpl(void* user_data_ctx, const char* text); -static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport* viewport, ImGuiPlatformImeData* data); +// Platform Dependents default implementation for ImGuiPlatformIO functions +static const char* Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext* ctx); +static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext* ctx, const char* text); +static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext* ctx, ImGuiViewport* viewport, ImGuiPlatformImeData* data); +static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext* ctx, const char* path); namespace ImGui { @@ -1141,6 +1229,7 @@ static bool NavScoreItem(ImGuiNavItemData* result); static void NavApplyItemToResult(ImGuiNavItemData* result); static void NavProcessItem(); static void NavProcessItemForTabbingRequest(ImGuiID id, ImGuiItemFlags item_flags, ImGuiNavMoveFlags move_flags); +static ImGuiInputSource NavCalcPreferredRefPosSource(); static ImVec2 NavCalcPreferredRefPos(); static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window); static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window); @@ -1150,9 +1239,11 @@ static int FindWindowFocusIndex(ImGuiWindow* window); // Error Checking and Debug Tools static void ErrorCheckNewFrameSanityChecks(); static void ErrorCheckEndFrameSanityChecks(); +#ifndef IMGUI_DISABLE_DEBUG_TOOLS static void UpdateDebugToolItemPicker(); static void UpdateDebugToolStackQueries(); static void UpdateDebugToolFlashStyleColor(); +#endif // Inputs static void UpdateKeyboardInputs(); @@ -1172,7 +1263,18 @@ static void SetLastItemDataForWindow(ImGuiWindow* window, const ImRe // Viewports const ImGuiID IMGUI_VIEWPORT_DEFAULT_ID = 0x11111111; // Using an arbitrary constant instead of e.g. ImHashStr("ViewportDefault", 0); so it's easier to spot in the debugger. The exact value doesn't matter. +static ImGuiViewportP* AddUpdateViewport(ImGuiWindow* window, ImGuiID id, const ImVec2& platform_pos, const ImVec2& size, ImGuiViewportFlags flags); +static void DestroyViewport(ImGuiViewportP* viewport); static void UpdateViewportsNewFrame(); +static void UpdateViewportsEndFrame(); +static void WindowSelectViewport(ImGuiWindow* window); +static void WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_window_in_stack); +static bool UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImGuiViewportP* host_viewport); +static bool UpdateTryMergeWindowIntoHostViewports(ImGuiWindow* window); +static bool GetWindowAlwaysWantOwnViewport(ImGuiWindow* window); +static int FindPlatformMonitorForPos(const ImVec2& pos); +static int FindPlatformMonitorForRect(const ImRect& r); +static void UpdateViewportPlatformMonitor(ImGuiViewportP* viewport); } @@ -1219,7 +1321,7 @@ static ImGuiMemFreeFunc GImAllocatorFreeFunc = FreeWrapper; static void* GImAllocatorUserData = NULL; //----------------------------------------------------------------------------- -// [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO) +// [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO, ImGuiPlatformIO) //----------------------------------------------------------------------------- ImGuiStyle::ImGuiStyle() @@ -1254,16 +1356,18 @@ ImGuiStyle::ImGuiStyle() TabBorderSize = 0.0f; // Thickness of border around tabs. TabMinWidthForCloseButton = 0.0f; // Minimum width for close button to appear on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected. TabBarBorderSize = 1.0f; // Thickness of tab-bar separator, which takes on the tab active color to denote focus. + TabBarOverlineSize = 2.0f; // Thickness of tab-bar overline, which highlights the selected tab-bar. TableAngledHeadersAngle = 35.0f * (IM_PI / 180.0f); // Angle of angled headers (supported values range from -50 degrees to +50 degrees). TableAngledHeadersTextAlign = ImVec2(0.5f,0.0f);// Alignment of angled headers within the cell ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. SelectableTextAlign = ImVec2(0.0f,0.0f);// Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. - SeparatorTextBorderSize = 3.0f; // Thickkness of border in SeparatorText() + SeparatorTextBorderSize = 3.0f; // Thickness of border in SeparatorText() SeparatorTextAlign = ImVec2(0.0f,0.5f);// Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center). SeparatorTextPadding = ImVec2(20.0f,3.f);// Horizontal offset of text from each edge of the separator + spacing on other axis. Generally small values. .y is recommended to be == FramePadding.y. DisplayWindowPadding = ImVec2(19,19); // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows. DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. + DockingSeparatorSize = 2.0f; // Thickness of resizing border between docked windows MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). @@ -1306,7 +1410,9 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor) LogSliderDeadzone = ImTrunc(LogSliderDeadzone * scale_factor); TabRounding = ImTrunc(TabRounding * scale_factor); TabMinWidthForCloseButton = (TabMinWidthForCloseButton != FLT_MAX) ? ImTrunc(TabMinWidthForCloseButton * scale_factor) : FLT_MAX; + TabBarOverlineSize = ImTrunc(TabBarOverlineSize * scale_factor); SeparatorTextPadding = ImTrunc(SeparatorTextPadding * scale_factor); + DockingSeparatorSize = ImTrunc(DockingSeparatorSize * scale_factor); DisplayWindowPadding = ImTrunc(DisplayWindowPadding * scale_factor); DisplaySafeAreaPadding = ImTrunc(DisplaySafeAreaPadding * scale_factor); MouseCursorScale = ImTrunc(MouseCursorScale * scale_factor); @@ -1326,10 +1432,6 @@ ImGuiIO::ImGuiIO() IniSavingRate = 5.0f; IniFilename = "imgui.ini"; // Important: "imgui.ini" is relative to current working dir, most apps will want to lock this to an absolute path (e.g. same path as executables). LogFilename = "imgui_log.txt"; -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - for (int i = 0; i < ImGuiKey_COUNT; i++) - KeyMap[i] = -1; -#endif UserData = NULL; Fonts = NULL; @@ -1338,11 +1440,26 @@ ImGuiIO::ImGuiIO() FontAllowUserScaling = false; DisplayFramebufferScale = ImVec2(1.0f, 1.0f); - MouseDoubleClickTime = 0.30f; - MouseDoubleClickMaxDist = 6.0f; - MouseDragThreshold = 6.0f; - KeyRepeatDelay = 0.275f; - KeyRepeatRate = 0.050f; + // Keyboard/Gamepad Navigation options + ConfigNavSwapGamepadButtons = false; + ConfigNavMoveSetMousePos = false; + ConfigNavCaptureKeyboard = true; + ConfigNavEscapeClearFocusItem = true; + ConfigNavEscapeClearFocusWindow = false; + ConfigNavCursorVisibleAuto = true; + ConfigNavCursorVisibleAlways = false; + + // Docking options (when ImGuiConfigFlags_DockingEnable is set) + ConfigDockingNoSplit = false; + ConfigDockingWithShift = false; + ConfigDockingAlwaysTabBar = false; + ConfigDockingTransparentPayload = false; + + // Viewport options (when ImGuiConfigFlags_ViewportsEnable is set) + ConfigViewportsNoAutoMerge = false; + ConfigViewportsNoTaskBarIcon = false; + ConfigViewportsNoDecoration = true; + ConfigViewportsNoDefaultParent = false; // Miscellaneous options MouseDrawCursor = false; @@ -1357,15 +1474,30 @@ ImGuiIO::ImGuiIO() ConfigDragClickToInputText = false; ConfigWindowsResizeFromEdges = true; ConfigWindowsMoveFromTitleBarOnly = false; + ConfigWindowsCopyContentsWithCtrlC = false; + ConfigScrollbarScrollByPage = true; ConfigMemoryCompactTimer = 60.0f; + ConfigDebugIsDebuggerPresent = false; + ConfigDebugHighlightIdConflicts = true; ConfigDebugBeginReturnValueOnce = false; ConfigDebugBeginReturnValueLoop = false; + ConfigErrorRecovery = true; + ConfigErrorRecoveryEnableAssert = true; + ConfigErrorRecoveryEnableDebugLog = true; + ConfigErrorRecoveryEnableTooltip = true; + + // Inputs Behaviors + MouseDoubleClickTime = 0.30f; + MouseDoubleClickMaxDist = 6.0f; + MouseDragThreshold = 6.0f; + KeyRepeatDelay = 0.275f; + KeyRepeatRate = 0.050f; + // Platform Functions // Note: Initialize() will setup default clipboard/ime handlers. BackendPlatformName = BackendRendererName = NULL; BackendPlatformUserData = BackendRendererUserData = BackendLanguageUserData = NULL; - PlatformLocaleDecimalPoint = '.'; // Input (NB: we already have memset zero the entire structure!) MousePos = ImVec2(-FLT_MAX, -FLT_MAX); @@ -1374,8 +1506,6 @@ ImGuiIO::ImGuiIO() for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f; for (int i = 0; i < IM_ARRAYSIZE(KeysData); i++) { KeysData[i].DownDuration = KeysData[i].DownDurationPrev = -1.0f; } AppAcceptingEvents = true; - BackendUsingLegacyKeyArrays = (ImS8)-1; - BackendUsingLegacyNavInputArray = true; // assume using legacy array until proven wrong } // Pass in translated ASCII characters for text input. @@ -1453,20 +1583,33 @@ void ImGuiIO::ClearEventsQueue() g.InputEventsQueue.clear(); } -// Clear current keyboard/mouse/gamepad state + current frame text input buffer. Equivalent to releasing all keys/buttons. +// Clear current keyboard/gamepad state + current frame text input buffer. Equivalent to releasing all keys/buttons. void ImGuiIO::ClearInputKeys() { -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - memset(KeysDown, 0, sizeof(KeysDown)); -#endif - for (int n = 0; n < IM_ARRAYSIZE(KeysData); n++) + ImGuiContext& g = *Ctx; + for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key++) { - KeysData[n].Down = false; - KeysData[n].DownDuration = -1.0f; - KeysData[n].DownDurationPrev = -1.0f; + if (ImGui::IsMouseKey((ImGuiKey)key)) + continue; + ImGuiKeyData* key_data = &g.IO.KeysData[key - ImGuiKey_NamedKey_BEGIN]; + key_data->Down = false; + key_data->DownDuration = -1.0f; + key_data->DownDurationPrev = -1.0f; } KeyCtrl = KeyShift = KeyAlt = KeySuper = false; KeyMods = ImGuiMod_None; + InputQueueCharacters.resize(0); // Behavior of old ClearInputCharacters(). +} + +void ImGuiIO::ClearInputMouse() +{ + for (ImGuiKey key = ImGuiKey_Mouse_BEGIN; key < ImGuiKey_Mouse_END; key = (ImGuiKey)(key + 1)) + { + ImGuiKeyData* key_data = &KeysData[key - ImGuiKey_NamedKey_BEGIN]; + key_data->Down = false; + key_data->DownDuration = -1.0f; + key_data->DownDurationPrev = -1.0f; + } MousePos = ImVec2(-FLT_MAX, -FLT_MAX); for (int n = 0; n < IM_ARRAYSIZE(MouseDown); n++) { @@ -1474,7 +1617,6 @@ void ImGuiIO::ClearInputKeys() MouseDownDuration[n] = MouseDownDurationPrev[n] = -1.0f; } MouseWheel = MouseWheelH = 0.0f; - InputQueueCharacters.resize(0); // Behavior of old ClearInputCharacters(). } // Removed this as it is ambiguous/misleading and generally incorrect to use with the existence of a higher-level input queue. @@ -1530,17 +1672,6 @@ void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value) else if (key == ImGuiKey_RightCtrl) { key = ImGuiKey_RightSuper; } } - // Verify that backend isn't mixing up using new io.AddKeyEvent() api and old io.KeysDown[] + io.KeyMap[] data. -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - IM_ASSERT((BackendUsingLegacyKeyArrays == -1 || BackendUsingLegacyKeyArrays == 0) && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!"); - if (BackendUsingLegacyKeyArrays == -1) - for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_NamedKey_END; n++) - IM_ASSERT(KeyMap[n] == -1 && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!"); - BackendUsingLegacyKeyArrays = 0; -#endif - if (ImGui::IsGamepadKey(key)) - BackendUsingLegacyNavInputArray = false; - // Filter duplicate (in particular: key mods and gamepad analog values are commonly spammed) const ImGuiInputEvent* latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_Key, (int)key); const ImGuiKeyData* key_data = ImGui::GetKeyData(&g, key); @@ -1576,20 +1707,10 @@ void ImGuiIO::SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native return; IM_ASSERT(ImGui::IsNamedKey(key)); // >= 512 IM_ASSERT(native_legacy_index == -1 || ImGui::IsLegacyKey((ImGuiKey)native_legacy_index)); // >= 0 && <= 511 - IM_UNUSED(native_keycode); // Yet unused - IM_UNUSED(native_scancode); // Yet unused - - // Build native->imgui map so old user code can still call key functions with native 0..511 values. -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - const int legacy_key = (native_legacy_index != -1) ? native_legacy_index : native_keycode; - if (!ImGui::IsLegacyKey((ImGuiKey)legacy_key)) - return; - KeyMap[legacy_key] = key; - KeyMap[key] = legacy_key; -#else - IM_UNUSED(key); - IM_UNUSED(native_legacy_index); -#endif + IM_UNUSED(key); // Yet unused + IM_UNUSED(native_keycode); // Yet unused + IM_UNUSED(native_scancode); // Yet unused + IM_UNUSED(native_legacy_index); // Yet unused } // Set master flag for accepting key/mouse/text events (default to true). Useful if you have native dialog boxes that are interrupting your application loop/refresh, and you want to disable events being queued while your app is frozen. @@ -1702,6 +1823,27 @@ void ImGuiIO::AddMouseSourceEvent(ImGuiMouseSource source) g.InputEventsNextMouseSource = source; } +void ImGuiIO::AddMouseViewportEvent(ImGuiID viewport_id) +{ + IM_ASSERT(Ctx != NULL); + ImGuiContext& g = *Ctx; + //IM_ASSERT(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport); + if (!AppAcceptingEvents) + return; + + // Filter duplicate + const ImGuiInputEvent* latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_MouseViewport); + const ImGuiID latest_viewport_id = latest_event ? latest_event->MouseViewport.HoveredViewportID : g.IO.MouseHoveredViewport; + if (latest_viewport_id == viewport_id) + return; + + ImGuiInputEvent e; + e.Type = ImGuiInputEventType_MouseViewport; + e.Source = ImGuiInputSource_Mouse; + e.MouseViewport.HoveredViewportID = viewport_id; + g.InputEventsQueue.push_back(e); +} + void ImGuiIO::AddFocusEvent(bool focused) { IM_ASSERT(Ctx != NULL); @@ -1720,6 +1862,13 @@ void ImGuiIO::AddFocusEvent(bool focused) g.InputEventsQueue.push_back(e); } +ImGuiPlatformIO::ImGuiPlatformIO() +{ + // Most fields are initialized with zero + memset(this, 0, sizeof(*this)); + Platform_LocaleDecimalPoint = '.'; +} + //----------------------------------------------------------------------------- // [SECTION] MISC HELPERS/UTILITIES (Geometry functions) //----------------------------------------------------------------------------- @@ -1910,7 +2059,7 @@ const char* ImStreolRange(const char* str, const char* str_end) return p ? p : str_end; } -const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line +const char* ImStrbol(const char* buf_mid_line, const char* buf_begin) // find beginning-of-line { while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n') buf_mid_line--; @@ -2028,6 +2177,10 @@ void ImFormatStringToTempBuffer(const char** out_buf, const char** out_buf_end, va_end(args); } +// FIXME: Should rework API toward allowing multiple in-flight temp buffers (easier and safer for caller) +// by making the caller acquire a temp buffer token, with either explicit or destructor release, e.g. +// ImGuiTempBufferToken token; +// ImFormatStringToTempBuffer(token, ...); void ImFormatStringToTempBufferV(const char** out_buf, const char** out_buf_end, const char* fmt, va_list args) { ImGuiContext& g = *GImGui; @@ -2059,11 +2212,14 @@ void ImFormatStringToTempBufferV(const char** out_buf, const char** out_buf_end, } } +#ifndef IMGUI_ENABLE_SSE4_2_CRC // CRC32 needs a 1KB lookup table (not cache friendly) // Although the code to generate the table is simple and shorter than the table itself, using a const table allows us to easily: // - avoid an unnecessary branch/memory tap, - keep the ImHashXXX functions usable by static constructors, - make it thread-safe. static const ImU32 GCrc32LookupTable[256] = { +#ifdef IMGUI_USE_LEGACY_CRC32_ADLER + // Legacy CRC32-adler table used pre 1.91.6 (before 2024/11/27). Only use if you cannot afford invalidating old .ini data. 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91, 0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5, 0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59, @@ -2080,7 +2236,27 @@ static const ImU32 GCrc32LookupTable[256] = 0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45, 0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9, 0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF,0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D, +#else + // CRC32c table compatible with SSE 4.2 instructions + 0x00000000,0xF26B8303,0xE13B70F7,0x1350F3F4,0xC79A971F,0x35F1141C,0x26A1E7E8,0xD4CA64EB,0x8AD958CF,0x78B2DBCC,0x6BE22838,0x9989AB3B,0x4D43CFD0,0xBF284CD3,0xAC78BF27,0x5E133C24, + 0x105EC76F,0xE235446C,0xF165B798,0x030E349B,0xD7C45070,0x25AFD373,0x36FF2087,0xC494A384,0x9A879FA0,0x68EC1CA3,0x7BBCEF57,0x89D76C54,0x5D1D08BF,0xAF768BBC,0xBC267848,0x4E4DFB4B, + 0x20BD8EDE,0xD2D60DDD,0xC186FE29,0x33ED7D2A,0xE72719C1,0x154C9AC2,0x061C6936,0xF477EA35,0xAA64D611,0x580F5512,0x4B5FA6E6,0xB93425E5,0x6DFE410E,0x9F95C20D,0x8CC531F9,0x7EAEB2FA, + 0x30E349B1,0xC288CAB2,0xD1D83946,0x23B3BA45,0xF779DEAE,0x05125DAD,0x1642AE59,0xE4292D5A,0xBA3A117E,0x4851927D,0x5B016189,0xA96AE28A,0x7DA08661,0x8FCB0562,0x9C9BF696,0x6EF07595, + 0x417B1DBC,0xB3109EBF,0xA0406D4B,0x522BEE48,0x86E18AA3,0x748A09A0,0x67DAFA54,0x95B17957,0xCBA24573,0x39C9C670,0x2A993584,0xD8F2B687,0x0C38D26C,0xFE53516F,0xED03A29B,0x1F682198, + 0x5125DAD3,0xA34E59D0,0xB01EAA24,0x42752927,0x96BF4DCC,0x64D4CECF,0x77843D3B,0x85EFBE38,0xDBFC821C,0x2997011F,0x3AC7F2EB,0xC8AC71E8,0x1C661503,0xEE0D9600,0xFD5D65F4,0x0F36E6F7, + 0x61C69362,0x93AD1061,0x80FDE395,0x72966096,0xA65C047D,0x5437877E,0x4767748A,0xB50CF789,0xEB1FCBAD,0x197448AE,0x0A24BB5A,0xF84F3859,0x2C855CB2,0xDEEEDFB1,0xCDBE2C45,0x3FD5AF46, + 0x7198540D,0x83F3D70E,0x90A324FA,0x62C8A7F9,0xB602C312,0x44694011,0x5739B3E5,0xA55230E6,0xFB410CC2,0x092A8FC1,0x1A7A7C35,0xE811FF36,0x3CDB9BDD,0xCEB018DE,0xDDE0EB2A,0x2F8B6829, + 0x82F63B78,0x709DB87B,0x63CD4B8F,0x91A6C88C,0x456CAC67,0xB7072F64,0xA457DC90,0x563C5F93,0x082F63B7,0xFA44E0B4,0xE9141340,0x1B7F9043,0xCFB5F4A8,0x3DDE77AB,0x2E8E845F,0xDCE5075C, + 0x92A8FC17,0x60C37F14,0x73938CE0,0x81F80FE3,0x55326B08,0xA759E80B,0xB4091BFF,0x466298FC,0x1871A4D8,0xEA1A27DB,0xF94AD42F,0x0B21572C,0xDFEB33C7,0x2D80B0C4,0x3ED04330,0xCCBBC033, + 0xA24BB5A6,0x502036A5,0x4370C551,0xB11B4652,0x65D122B9,0x97BAA1BA,0x84EA524E,0x7681D14D,0x2892ED69,0xDAF96E6A,0xC9A99D9E,0x3BC21E9D,0xEF087A76,0x1D63F975,0x0E330A81,0xFC588982, + 0xB21572C9,0x407EF1CA,0x532E023E,0xA145813D,0x758FE5D6,0x87E466D5,0x94B49521,0x66DF1622,0x38CC2A06,0xCAA7A905,0xD9F75AF1,0x2B9CD9F2,0xFF56BD19,0x0D3D3E1A,0x1E6DCDEE,0xEC064EED, + 0xC38D26C4,0x31E6A5C7,0x22B65633,0xD0DDD530,0x0417B1DB,0xF67C32D8,0xE52CC12C,0x1747422F,0x49547E0B,0xBB3FFD08,0xA86F0EFC,0x5A048DFF,0x8ECEE914,0x7CA56A17,0x6FF599E3,0x9D9E1AE0, + 0xD3D3E1AB,0x21B862A8,0x32E8915C,0xC083125F,0x144976B4,0xE622F5B7,0xF5720643,0x07198540,0x590AB964,0xAB613A67,0xB831C993,0x4A5A4A90,0x9E902E7B,0x6CFBAD78,0x7FAB5E8C,0x8DC0DD8F, + 0xE330A81A,0x115B2B19,0x020BD8ED,0xF0605BEE,0x24AA3F05,0xD6C1BC06,0xC5914FF2,0x37FACCF1,0x69E9F0D5,0x9B8273D6,0x88D28022,0x7AB90321,0xAE7367CA,0x5C18E4C9,0x4F48173D,0xBD23943E, + 0xF36E6F75,0x0105EC76,0x12551F82,0xE03E9C81,0x34F4F86A,0xC69F7B69,0xD5CF889D,0x27A40B9E,0x79B737BA,0x8BDCB4B9,0x988C474D,0x6AE7C44E,0xBE2DA0A5,0x4C4623A6,0x5F16D052,0xAD7D5351 +#endif }; +#endif // Known size hash // It is ok to call ImHashData on a string with known length but the ### operator won't be supported. @@ -2089,10 +2265,22 @@ ImGuiID ImHashData(const void* data_p, size_t data_size, ImGuiID seed) { ImU32 crc = ~seed; const unsigned char* data = (const unsigned char*)data_p; + const unsigned char *data_end = (const unsigned char*)data_p + data_size; +#ifndef IMGUI_ENABLE_SSE4_2_CRC const ImU32* crc32_lut = GCrc32LookupTable; - while (data_size-- != 0) + while (data < data_end) crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *data++]; return ~crc; +#else + while (data + 4 <= data_end) + { + crc = _mm_crc32_u32(crc, *(ImU32*)data); + data += 4; + } + while (data < data_end) + crc = _mm_crc32_u8(crc, *data++); + return ~crc; +#endif } // Zero-terminated string hash, with support for ### to reset back to seed value @@ -2106,7 +2294,9 @@ ImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed) seed = ~seed; ImU32 crc = seed; const unsigned char* data = (const unsigned char*)data_p; +#ifndef IMGUI_ENABLE_SSE4_2_CRC const ImU32* crc32_lut = GCrc32LookupTable; +#endif if (data_size != 0) { while (data_size-- != 0) @@ -2114,7 +2304,11 @@ ImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed) unsigned char c = *data++; if (c == '#' && data_size >= 2 && data[0] == '#' && data[1] == '#') crc = seed; +#ifndef IMGUI_ENABLE_SSE4_2_CRC crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; +#else + crc = _mm_crc32_u8(crc, c); +#endif } } else @@ -2123,7 +2317,11 @@ ImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed) { if (c == '#' && data[0] == '#' && data[1] == '#') crc = seed; +#ifndef IMGUI_ENABLE_SSE4_2_CRC crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; +#else + crc = _mm_crc32_u8(crc, c); +#endif } } return ~crc; @@ -2525,26 +2723,25 @@ ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStoragePair* in_ return in_p; } +IM_MSVC_RUNTIME_CHECKS_OFF +static int IMGUI_CDECL PairComparerByID(const void* lhs, const void* rhs) +{ + // We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with that. + ImGuiID lhs_v = ((const ImGuiStoragePair*)lhs)->key; + ImGuiID rhs_v = ((const ImGuiStoragePair*)rhs)->key; + return (lhs_v > rhs_v ? +1 : lhs_v < rhs_v ? -1 : 0); +} + // For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once. void ImGuiStorage::BuildSortByKey() { - struct StaticFunc - { - static int IMGUI_CDECL PairComparerByID(const void* lhs, const void* rhs) - { - // We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with that. - if (((const ImGuiStoragePair*)lhs)->key > ((const ImGuiStoragePair*)rhs)->key) return +1; - if (((const ImGuiStoragePair*)lhs)->key < ((const ImGuiStoragePair*)rhs)->key) return -1; - return 0; - } - }; - ImQsort(Data.Data, (size_t)Data.Size, sizeof(ImGuiStoragePair), StaticFunc::PairComparerByID); + ImQsort(Data.Data, (size_t)Data.Size, sizeof(ImGuiStoragePair), PairComparerByID); } int ImGuiStorage::GetInt(ImGuiID key, int default_val) const { ImGuiStoragePair* it = ImLowerBound(const_cast(Data.Data), const_cast(Data.Data + Data.Size), key); - if (it == Data.end() || it->key != key) + if (it == Data.Data + Data.Size || it->key != key) return default_val; return it->val_i; } @@ -2557,7 +2754,7 @@ bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const { ImGuiStoragePair* it = ImLowerBound(const_cast(Data.Data), const_cast(Data.Data + Data.Size), key); - if (it == Data.end() || it->key != key) + if (it == Data.Data + Data.Size || it->key != key) return default_val; return it->val_f; } @@ -2565,7 +2762,7 @@ float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const void* ImGuiStorage::GetVoidPtr(ImGuiID key) const { ImGuiStoragePair* it = ImLowerBound(const_cast(Data.Data), const_cast(Data.Data + Data.Size), key); - if (it == Data.end() || it->key != key) + if (it == Data.Data + Data.Size || it->key != key) return NULL; return it->val_p; } @@ -2574,7 +2771,7 @@ void* ImGuiStorage::GetVoidPtr(ImGuiID key) const int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val) { ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key); - if (it == Data.end() || it->key != key) + if (it == Data.Data + Data.Size || it->key != key) it = Data.insert(it, ImGuiStoragePair(key, default_val)); return &it->val_i; } @@ -2587,7 +2784,7 @@ bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val) float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val) { ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key); - if (it == Data.end() || it->key != key) + if (it == Data.Data + Data.Size || it->key != key) it = Data.insert(it, ImGuiStoragePair(key, default_val)); return &it->val_f; } @@ -2595,7 +2792,7 @@ float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val) void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val) { ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key); - if (it == Data.end() || it->key != key) + if (it == Data.Data + Data.Size || it->key != key) it = Data.insert(it, ImGuiStoragePair(key, default_val)); return &it->val_p; } @@ -2604,7 +2801,7 @@ void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val) void ImGuiStorage::SetInt(ImGuiID key, int val) { ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key); - if (it == Data.end() || it->key != key) + if (it == Data.Data + Data.Size || it->key != key) Data.insert(it, ImGuiStoragePair(key, val)); else it->val_i = val; @@ -2618,7 +2815,7 @@ void ImGuiStorage::SetBool(ImGuiID key, bool val) void ImGuiStorage::SetFloat(ImGuiID key, float val) { ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key); - if (it == Data.end() || it->key != key) + if (it == Data.Data + Data.Size || it->key != key) Data.insert(it, ImGuiStoragePair(key, val)); else it->val_f = val; @@ -2627,7 +2824,7 @@ void ImGuiStorage::SetFloat(ImGuiID key, float val) void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val) { ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key); - if (it == Data.end() || it->key != key) + if (it == Data.Data + Data.Size || it->key != key) Data.insert(it, ImGuiStoragePair(key, val)); else it->val_p = val; @@ -2638,6 +2835,7 @@ void ImGuiStorage::SetAllInt(int v) for (int i = 0; i < Data.Size; i++) Data[i].val_i = v; } +IM_MSVC_RUNTIME_CHECKS_RESTORE //----------------------------------------------------------------------------- // [SECTION] ImGuiTextFilter @@ -2705,15 +2903,15 @@ void ImGuiTextFilter::Build() bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const { - if (Filters.empty()) + if (Filters.Size == 0) return true; if (text == NULL) - text = ""; + text = text_end = ""; for (const ImGuiTextRange& f : Filters) { - if (f.empty()) + if (f.b == f.e) continue; if (f.b[0] == '-') { @@ -2880,15 +3078,6 @@ static void ImGuiListClipper_SeekCursorAndSetupPrevLine(float pos_y, float line_ } } -static void ImGuiListClipper_SeekCursorForItem(ImGuiListClipper* clipper, int item_n) -{ - // StartPosY starts from ItemsFrozen hence the subtraction - // Perform the add and multiply with double to allow seeking through larger ranges - ImGuiListClipperData* data = (ImGuiListClipperData*)clipper->TempData; - float pos_y = (float)((double)clipper->StartPosY + data->LossynessOffset + (double)(item_n - data->ItemsFrozen) * clipper->ItemsHeight); - ImGuiListClipper_SeekCursorAndSetupPrevLine(pos_y, clipper->ItemsHeight); -} - ImGuiListClipper::ImGuiListClipper() { memset(this, 0, sizeof(*this)); @@ -2925,6 +3114,7 @@ void ImGuiListClipper::Begin(int items_count, float items_height) data->Reset(this); data->LossynessOffset = window->DC.CursorStartPosLossyness.y; TempData = data; + StartSeekOffsetY = data->LossynessOffset; } void ImGuiListClipper::End() @@ -2935,7 +3125,7 @@ void ImGuiListClipper::End() ImGuiContext& g = *Ctx; IMGUI_DEBUG_LOG_CLIPPER("Clipper: End() in '%s'\n", g.CurrentWindow->Name); if (ItemsCount >= 0 && ItemsCount < INT_MAX && DisplayStart >= 0) - ImGuiListClipper_SeekCursorForItem(this, ItemsCount); + SeekCursorForItem(ItemsCount); // Restore temporary buffer and fix back pointers which may be invalidated when nesting IM_ASSERT(data->ListClipper == this); @@ -2959,6 +3149,17 @@ void ImGuiListClipper::IncludeItemsByIndex(int item_begin, int item_end) data->Ranges.push_back(ImGuiListClipperRange::FromIndices(item_begin, item_end)); } +// This is already called while stepping. +// The ONLY reason you may want to call this is if you passed INT_MAX to ImGuiListClipper::Begin() because you couldn't step item count beforehand. +void ImGuiListClipper::SeekCursorForItem(int item_n) +{ + // - Perform the add and multiply with double to allow seeking through larger ranges. + // - StartPosY starts from ItemsFrozen, by adding SeekOffsetY we generally cancel that out (SeekOffsetY == LossynessOffset - ItemsFrozen * ItemsHeight). + // - The reason we store SeekOffsetY instead of inferring it, is because we want to allow user to perform Seek after the last step, where ImGuiListClipperData is already done. + float pos_y = (float)((double)StartPosY + StartSeekOffsetY + (double)item_n * ItemsHeight); + ImGuiListClipper_SeekCursorAndSetupPrevLine(pos_y, ItemsHeight); +} + static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) { ImGuiContext& g = *clipper->Ctx; @@ -3013,7 +3214,8 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) bool affected_by_floating_point_precision = ImIsFloatAboveGuaranteedIntegerPrecision(clipper->StartPosY) || ImIsFloatAboveGuaranteedIntegerPrecision(window->DC.CursorPos.y); if (affected_by_floating_point_precision) clipper->ItemsHeight = window->DC.PrevLineSize.y + g.Style.ItemSpacing.y; // FIXME: Technically wouldn't allow multi-line entries. - + if (clipper->ItemsHeight == 0.0f && clipper->ItemsCount == INT_MAX) // Accept that no item have been submitted if in indeterminate mode. + return false; IM_ASSERT(clipper->ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!"); calc_clipping = true; // If item height had to be calculated, calculate clipping afterwards. } @@ -3022,6 +3224,9 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) const int already_submitted = clipper->DisplayEnd; if (calc_clipping) { + // Record seek offset, this is so ImGuiListClipper::Seek() can be called after ImGuiListClipperData is done + clipper->StartSeekOffsetY = (double)data->LossynessOffset - data->ItemsFrozen * (double)clipper->ItemsHeight; + if (g.LogEnabled) { // If logging is active, do not perform any clipping @@ -3042,9 +3247,27 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) data->Ranges.push_back(ImGuiListClipperRange::FromPositions(nav_rect_abs.Min.y, nav_rect_abs.Max.y, 0, 0)); // Add visible range + float min_y = window->ClipRect.Min.y; + float max_y = window->ClipRect.Max.y; + + // Add box selection range + ImGuiBoxSelectState* bs = &g.BoxSelectState; + if (bs->IsActive && bs->Window == window) + { + // FIXME: Selectable() use of half-ItemSpacing isn't consistent in matter of layout, as ItemAdd(bb) stray above ItemSize()'s CursorPos. + // RangeSelect's BoxSelect relies on comparing overlap of previous and current rectangle and is sensitive to that. + // As a workaround we currently half ItemSpacing worth on each side. + min_y -= g.Style.ItemSpacing.y; + max_y += g.Style.ItemSpacing.y; + + // Box-select on 2D area requires different clipping. + if (bs->UnclipMode) + data->Ranges.push_back(ImGuiListClipperRange::FromPositions(bs->UnclipRect.Min.y, bs->UnclipRect.Max.y, 0, 0)); + } + const int off_min = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Up) ? -1 : 0; const int off_max = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Down) ? 1 : 0; - data->Ranges.push_back(ImGuiListClipperRange::FromPositions(window->ClipRect.Min.y, window->ClipRect.Max.y, off_min, off_max)); + data->Ranges.push_back(ImGuiListClipperRange::FromPositions(min_y, max_y, off_min, off_max)); } // Convert position ranges to item index ranges @@ -3069,7 +3292,7 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) clipper->DisplayStart = ImMax(data->Ranges[data->StepNo].Min, already_submitted); clipper->DisplayEnd = ImMin(data->Ranges[data->StepNo].Max, clipper->ItemsCount); if (clipper->DisplayStart > already_submitted) //-V1051 - ImGuiListClipper_SeekCursorForItem(clipper, clipper->DisplayStart); + clipper->SeekCursorForItem(clipper->DisplayStart); data->StepNo++; if (clipper->DisplayStart == clipper->DisplayEnd && data->StepNo < data->Ranges.Size) continue; @@ -3079,7 +3302,7 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) // After the last step: Let the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), // Advance the cursor to the end of the list and then returns 'false' to end the loop. if (clipper->ItemsCount < INT_MAX) - ImGuiListClipper_SeekCursorForItem(clipper, clipper->ItemsCount); + clipper->SeekCursorForItem(clipper->ItemsCount); return false; } @@ -3178,7 +3401,7 @@ void ImGui::PopStyleColor(int count) ImGuiContext& g = *GImGui; if (g.ColorStack.Size < count) { - IM_ASSERT_USER_ERROR(g.ColorStack.Size > count, "Calling PopStyleColor() too many times!"); + IM_ASSERT_USER_ERROR(0, "Calling PopStyleColor() too many times!"); count = g.ColorStack.Size; } while (count > 0) @@ -3190,6 +3413,11 @@ void ImGui::PopStyleColor(int count) } } +static const ImGuiCol GWindowDockStyleColors[ImGuiWindowDockStyleCol_COUNT] = +{ + ImGuiCol_Text, ImGuiCol_TabHovered, ImGuiCol_Tab, ImGuiCol_TabSelected, ImGuiCol_TabSelectedOverline, ImGuiCol_TabDimmed, ImGuiCol_TabDimmedSelected, ImGuiCol_TabDimmedSelectedOverline, +}; + static const ImGuiDataVarInfo GStyleVarInfo[] = { { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha @@ -3217,6 +3445,7 @@ static const ImGuiDataVarInfo 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 @@ -3224,6 +3453,7 @@ static const ImGuiDataVarInfo GStyleVarInfo[] = { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, SeparatorTextBorderSize)}, // ImGuiStyleVar_SeparatorTextBorderSize { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextAlign) }, // ImGuiStyleVar_SeparatorTextAlign { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextPadding) }, // ImGuiStyleVar_SeparatorTextPadding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, DockingSeparatorSize) }, // ImGuiStyleVar_DockingSeparatorSize }; const ImGuiDataVarInfo* ImGui::GetStyleVarInfo(ImGuiStyleVar idx) @@ -3237,28 +3467,56 @@ void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) { ImGuiContext& g = *GImGui; const ImGuiDataVarInfo* var_info = GetStyleVarInfo(idx); - if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1) + if (var_info->Type != ImGuiDataType_Float || var_info->Count != 1) + { + IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!"); + return; + } + float* pvar = (float*)var_info->GetVarPtr(&g.Style); + g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); + *pvar = val; +} + +void ImGui::PushStyleVarX(ImGuiStyleVar idx, float val_x) +{ + ImGuiContext& g = *GImGui; + const ImGuiDataVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->Type != ImGuiDataType_Float || var_info->Count != 2) + { + IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!"); + return; + } + ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style); + g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); + pvar->x = val_x; +} + +void ImGui::PushStyleVarY(ImGuiStyleVar idx, float val_y) +{ + ImGuiContext& g = *GImGui; + const ImGuiDataVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->Type != ImGuiDataType_Float || var_info->Count != 2) { - float* pvar = (float*)var_info->GetVarPtr(&g.Style); - g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); - *pvar = val; + IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!"); return; } - IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!"); + ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style); + g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); + pvar->y = val_y; } void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) { ImGuiContext& g = *GImGui; const ImGuiDataVarInfo* var_info = GetStyleVarInfo(idx); - if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2) + if (var_info->Type != ImGuiDataType_Float || var_info->Count != 2) { - ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style); - g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); - *pvar = val; + IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!"); return; } - IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!"); + ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style); + g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); + *pvar = val; } void ImGui::PopStyleVar(int count) @@ -3266,7 +3524,7 @@ void ImGui::PopStyleVar(int count) ImGuiContext& g = *GImGui; if (g.StyleVarStack.Size < count) { - IM_ASSERT_USER_ERROR(g.StyleVarStack.Size > count, "Calling PopStyleVar() too many times!"); + IM_ASSERT_USER_ERROR(0, "Calling PopStyleVar() too many times!"); count = g.StyleVarStack.Size; } while (count > 0) @@ -3320,11 +3578,15 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) case ImGuiCol_ResizeGrip: return "ResizeGrip"; case ImGuiCol_ResizeGripHovered: return "ResizeGripHovered"; case ImGuiCol_ResizeGripActive: return "ResizeGripActive"; - case ImGuiCol_Tab: return "Tab"; case ImGuiCol_TabHovered: return "TabHovered"; - case ImGuiCol_TabActive: return "TabActive"; - case ImGuiCol_TabUnfocused: return "TabUnfocused"; - case ImGuiCol_TabUnfocusedActive: return "TabUnfocusedActive"; + case ImGuiCol_Tab: return "Tab"; + case ImGuiCol_TabSelected: return "TabSelected"; + case ImGuiCol_TabSelectedOverline: return "TabSelectedOverline"; + case ImGuiCol_TabDimmed: return "TabDimmed"; + case ImGuiCol_TabDimmedSelected: return "TabDimmedSelected"; + case ImGuiCol_TabDimmedSelectedOverline: return "TabDimmedSelectedOverline"; + case ImGuiCol_DockingPreview: return "DockingPreview"; + case ImGuiCol_DockingEmptyBg: return "DockingEmptyBg"; case ImGuiCol_PlotLines: return "PlotLines"; case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered"; case ImGuiCol_PlotHistogram: return "PlotHistogram"; @@ -3334,9 +3596,10 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) case ImGuiCol_TableBorderLight: return "TableBorderLight"; case ImGuiCol_TableRowBg: return "TableRowBg"; case ImGuiCol_TableRowBgAlt: return "TableRowBgAlt"; + case ImGuiCol_TextLink: return "TextLink"; case ImGuiCol_TextSelectedBg: return "TextSelectedBg"; case ImGuiCol_DragDropTarget: return "DragDropTarget"; - case ImGuiCol_NavHighlight: return "NavHighlight"; + case ImGuiCol_NavCursor: return "NavCursor"; case ImGuiCol_NavWindowingHighlight: return "NavWindowingHighlight"; case ImGuiCol_NavWindowingDimBg: return "NavWindowingDimBg"; case ImGuiCol_ModalWindowDimBg: return "ModalWindowDimBg"; @@ -3477,9 +3740,9 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con // min max ellipsis_max // <-> this is generally some padding value - const ImFont* font = draw_list->_Data->Font; + ImFont* font = draw_list->_Data->Font; const float font_size = draw_list->_Data->FontSize; - const float font_scale = font_size / font->FontSize; + const float font_scale = draw_list->_Data->FontScale; const char* text_end_ellipsis = NULL; const float ellipsis_width = font->EllipsisWidth * font_scale; @@ -3516,13 +3779,13 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con } // Render a rectangle shaped with optional rounding and borders -void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding) +void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool borders, float rounding) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding); const float border_size = g.Style.FrameBorderSize; - if (border && border_size > 0.0f) + if (borders && border_size > 0.0f) { window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, 0, border_size); window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size); @@ -3541,24 +3804,26 @@ void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) } } -void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags) +void ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags) { ImGuiContext& g = *GImGui; if (id != g.NavId) return; - if (g.NavDisableHighlight && !(flags & ImGuiNavHighlightFlags_AlwaysDraw)) + if (!g.NavCursorVisible && !(flags & ImGuiNavRenderCursorFlags_AlwaysDraw)) + return; + if (id == g.LastItemData.ID && (g.LastItemData.ItemFlags & ImGuiItemFlags_NoNav)) return; ImGuiWindow* window = g.CurrentWindow; if (window->DC.NavHideHighlightOneFrame) return; - float rounding = (flags & ImGuiNavHighlightFlags_NoRounding) ? 0.0f : g.Style.FrameRounding; + float rounding = (flags & ImGuiNavRenderCursorFlags_NoRounding) ? 0.0f : g.Style.FrameRounding; ImRect display_rect = bb; display_rect.ClipWith(window->ClipRect); const float thickness = 2.0f; - if (flags & ImGuiNavHighlightFlags_Compact) + if (flags & ImGuiNavRenderCursorFlags_Compact) { - window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, 0, thickness); + window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, 0, thickness); } else { @@ -3567,7 +3832,7 @@ void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFl bool fully_visible = window->ClipRect.Contains(display_rect); if (!fully_visible) window->DrawList->PushClipRect(display_rect.Min, display_rect.Max); - window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, 0, thickness); + window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, 0, thickness); if (!fully_visible) window->DrawList->PopClipRect(); } @@ -3576,7 +3841,8 @@ void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFl void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCursor mouse_cursor, ImU32 col_fill, ImU32 col_border, ImU32 col_shadow) { ImGuiContext& g = *GImGui; - IM_ASSERT(mouse_cursor > ImGuiMouseCursor_None && mouse_cursor < ImGuiMouseCursor_COUNT); + if (mouse_cursor <= ImGuiMouseCursor_None || mouse_cursor >= ImGuiMouseCursor_COUNT) // We intentionally accept out of bound values. + mouse_cursor = ImGuiMouseCursor_Arrow; ImFontAtlas* font_atlas = g.DrawListSharedData.Font->ContainerAtlas; for (ImGuiViewportP* viewport : g.Viewports) { @@ -3585,7 +3851,7 @@ void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCurso if (!font_atlas->GetMouseCursorTexData(mouse_cursor, &offset, &size, &uv[0], &uv[2])) continue; const ImVec2 pos = base_pos - offset; - const float scale = base_scale; + const float scale = base_scale * viewport->DpiScale; if (!viewport->GetMainRect().Overlaps(ImRect(pos, pos + ImVec2(size.x + 2, size.y + 2) * scale))) continue; ImDrawList* draw_list = GetForegroundDrawList(viewport); @@ -3656,7 +3922,7 @@ void ImGui::DestroyContext(ImGuiContext* ctx) IM_DELETE(ctx); } -// IMPORTANT: ###xxx suffixes must be same in ALL languages +// IMPORTANT: interactive elements requires a fixed ###xxx suffix, it must be same in ALL languages to allow for automation. static const ImGuiLocEntry GLocalizationEntriesEnUS[] = { { ImGuiLocKey_VersionStr, "Dear ImGui " IMGUI_VERSION " (" IM_STRINGIFY(IMGUI_VERSION_NUM) ")" }, @@ -3667,8 +3933,240 @@ static const ImGuiLocEntry GLocalizationEntriesEnUS[] = { ImGuiLocKey_WindowingMainMenuBar, "(Main menu bar)" }, { ImGuiLocKey_WindowingPopup, "(Popup)" }, { ImGuiLocKey_WindowingUntitled, "(Untitled)" }, + { ImGuiLocKey_OpenLink_s, "Open '%s'" }, + { ImGuiLocKey_CopyLink, "Copy Link###CopyLink" }, + { ImGuiLocKey_DockingHideTabBar, "Hide tab bar###HideTabBar" }, + { ImGuiLocKey_DockingHoldShiftToDock, "Hold SHIFT to enable Docking window." }, + { ImGuiLocKey_DockingDragToUndockOrMoveNode,"Click and drag to move or undock whole node." }, }; +ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) +{ + IO.Ctx = this; + InputTextState.Ctx = this; + + Initialized = false; + ConfigFlagsCurrFrame = ConfigFlagsLastFrame = ImGuiConfigFlags_None; + FontAtlasOwnedByContext = shared_font_atlas ? false : true; + Font = NULL; + FontSize = FontBaseSize = FontScale = CurrentDpiScale = 0.0f; + IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); + Time = 0.0f; + FrameCount = 0; + FrameCountEnded = FrameCountPlatformEnded = FrameCountRendered = -1; + WithinFrameScope = WithinFrameScopeWithImplicitWindow = WithinEndChild = false; + GcCompactAll = false; + TestEngineHookItems = false; + TestEngine = NULL; + memset(ContextName, 0, sizeof(ContextName)); + + InputEventsNextMouseSource = ImGuiMouseSource_Mouse; + InputEventsNextEventId = 1; + + WindowsActiveCount = 0; + CurrentWindow = NULL; + HoveredWindow = NULL; + HoveredWindowUnderMovingWindow = NULL; + HoveredWindowBeforeClear = NULL; + MovingWindow = NULL; + WheelingWindow = NULL; + WheelingWindowStartFrame = WheelingWindowScrolledFrame = -1; + WheelingWindowReleaseTimer = 0.0f; + + DebugDrawIdConflicts = 0; + DebugHookIdInfo = 0; + HoveredId = HoveredIdPreviousFrame = 0; + HoveredIdPreviousFrameItemCount = 0; + HoveredIdAllowOverlap = false; + HoveredIdIsDisabled = false; + HoveredIdTimer = HoveredIdNotActiveTimer = 0.0f; + ItemUnclipByLog = false; + ActiveId = 0; + ActiveIdIsAlive = 0; + ActiveIdTimer = 0.0f; + ActiveIdIsJustActivated = false; + ActiveIdAllowOverlap = false; + ActiveIdNoClearOnFocusLoss = false; + ActiveIdHasBeenPressedBefore = false; + ActiveIdHasBeenEditedBefore = false; + ActiveIdHasBeenEditedThisFrame = false; + ActiveIdFromShortcut = false; + ActiveIdClickOffset = ImVec2(-1, -1); + ActiveIdWindow = NULL; + ActiveIdSource = ImGuiInputSource_None; + ActiveIdMouseButton = -1; + ActiveIdPreviousFrame = 0; + ActiveIdPreviousFrameIsAlive = false; + ActiveIdPreviousFrameHasBeenEditedBefore = false; + ActiveIdPreviousFrameWindow = NULL; + LastActiveId = 0; + LastActiveIdTimer = 0.0f; + + LastKeyboardKeyPressTime = LastKeyModsChangeTime = LastKeyModsChangeFromNoneTime = -1.0; + + ActiveIdUsingNavDirMask = 0x00; + ActiveIdUsingAllKeyboardKeys = false; + + CurrentFocusScopeId = 0; + CurrentItemFlags = ImGuiItemFlags_None; + DebugShowGroupRects = false; + + CurrentViewport = NULL; + MouseViewport = MouseLastHoveredViewport = NULL; + PlatformLastFocusedViewportId = 0; + ViewportCreatedCount = PlatformWindowsCreatedCount = 0; + ViewportFocusedStampCount = 0; + + NavCursorVisible = false; + NavHighlightItemUnderNav = false; + NavMousePosDirty = false; + NavIdIsAlive = false; + NavId = 0; + NavWindow = NULL; + NavFocusScopeId = NavActivateId = NavActivateDownId = NavActivatePressedId = 0; + NavLayer = ImGuiNavLayer_Main; + NavNextActivateId = 0; + NavActivateFlags = NavNextActivateFlags = ImGuiActivateFlags_None; + NavHighlightActivatedId = 0; + NavHighlightActivatedTimer = 0.0f; + NavInputSource = ImGuiInputSource_Keyboard; + NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid; + NavCursorHideFrames = 0; + + NavAnyRequest = false; + NavInitRequest = false; + NavInitRequestFromMove = false; + NavMoveSubmitted = false; + NavMoveScoringItems = false; + NavMoveForwardToNextFrame = false; + NavMoveFlags = ImGuiNavMoveFlags_None; + NavMoveScrollFlags = ImGuiScrollFlags_None; + NavMoveKeyMods = ImGuiMod_None; + NavMoveDir = NavMoveDirForDebug = NavMoveClipDir = ImGuiDir_None; + NavScoringDebugCount = 0; + NavTabbingDir = 0; + NavTabbingCounter = 0; + + NavJustMovedFromFocusScopeId = NavJustMovedToId = NavJustMovedToFocusScopeId = 0; + NavJustMovedToKeyMods = ImGuiMod_None; + NavJustMovedToIsTabbing = false; + NavJustMovedToHasSelectionData = false; + + // All platforms use Ctrl+Tab but Ctrl<>Super are swapped on Mac... + // FIXME: Because this value is stored, it annoyingly interfere with toggling io.ConfigMacOSXBehaviors updating this.. + ConfigNavWindowingKeyNext = IO.ConfigMacOSXBehaviors ? (ImGuiMod_Super | ImGuiKey_Tab) : (ImGuiMod_Ctrl | ImGuiKey_Tab); + ConfigNavWindowingKeyPrev = IO.ConfigMacOSXBehaviors ? (ImGuiMod_Super | ImGuiMod_Shift | ImGuiKey_Tab) : (ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab); + NavWindowingTarget = NavWindowingTargetAnim = NavWindowingListWindow = NULL; + NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f; + NavWindowingToggleLayer = false; + NavWindowingToggleKey = ImGuiKey_None; + + DimBgRatio = 0.0f; + + DragDropActive = DragDropWithinSource = DragDropWithinTarget = false; + DragDropSourceFlags = ImGuiDragDropFlags_None; + DragDropSourceFrameCount = -1; + DragDropMouseButton = -1; + DragDropTargetId = 0; + DragDropAcceptFlags = ImGuiDragDropFlags_None; + DragDropAcceptIdCurrRectSurface = 0.0f; + DragDropAcceptIdPrev = DragDropAcceptIdCurr = 0; + DragDropAcceptFrameCount = -1; + DragDropHoldJustPressedId = 0; + memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal)); + + ClipperTempDataStacked = 0; + + CurrentTable = NULL; + TablesTempDataStacked = 0; + CurrentTabBar = NULL; + CurrentMultiSelect = NULL; + MultiSelectTempDataStacked = 0; + + HoverItemDelayId = HoverItemDelayIdPreviousFrame = HoverItemUnlockedStationaryId = HoverWindowUnlockedStationaryId = 0; + HoverItemDelayTimer = HoverItemDelayClearTimer = 0.0f; + + MouseCursor = ImGuiMouseCursor_Arrow; + MouseStationaryTimer = 0.0f; + + TempInputId = 0; + memset(&DataTypeZeroValue, 0, sizeof(DataTypeZeroValue)); + BeginMenuDepth = BeginComboDepth = 0; + ColorEditOptions = ImGuiColorEditFlags_DefaultOptions_; + ColorEditCurrentID = ColorEditSavedID = 0; + ColorEditSavedHue = ColorEditSavedSat = 0.0f; + ColorEditSavedColor = 0; + WindowResizeRelativeMode = false; + ScrollbarSeekMode = 0; + ScrollbarClickDeltaToGrabCenter = 0.0f; + SliderGrabClickOffset = 0.0f; + SliderCurrentAccum = 0.0f; + SliderCurrentAccumDirty = false; + DragCurrentAccumDirty = false; + DragCurrentAccum = 0.0f; + DragSpeedDefaultRatio = 1.0f / 100.0f; + DisabledAlphaBackup = 0.0f; + DisabledStackSize = 0; + TooltipOverrideCount = 0; + TooltipPreviousWindow = NULL; + + PlatformImeData.InputPos = ImVec2(0.0f, 0.0f); + PlatformImeDataPrev.InputPos = ImVec2(-1.0f, -1.0f); // Different to ensure initial submission + PlatformImeViewport = 0; + + DockNodeWindowMenuHandler = NULL; + + SettingsLoaded = false; + SettingsDirtyTimer = 0.0f; + HookIdNext = 0; + + memset(LocalizationTable, 0, sizeof(LocalizationTable)); + + LogEnabled = false; + LogFlags = ImGuiLogFlags_None; + LogWindow = NULL; + LogNextPrefix = LogNextSuffix = NULL; + LogFile = NULL; + LogLinePosY = FLT_MAX; + LogLineFirstItem = false; + LogDepthRef = 0; + LogDepthToExpand = LogDepthToExpandDefault = 2; + + ErrorCallback = NULL; + ErrorCallbackUserData = NULL; + ErrorFirst = true; + ErrorCountCurrentFrame = 0; + StackSizesInBeginForCurrentWindow = NULL; + + DebugDrawIdConflictsCount = 0; + DebugLogFlags = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_OutputToTTY | ImGuiDebugLogFlags_EventFont; + DebugLocateId = 0; + DebugLogSkippedErrors = 0; + DebugLogAutoDisableFlags = ImGuiDebugLogFlags_None; + DebugLogAutoDisableFrames = 0; + DebugLocateFrames = 0; + DebugBeginReturnValueCullDepth = -1; + DebugItemPickerActive = false; + DebugItemPickerMouseButton = ImGuiMouseButton_Left; + DebugItemPickerBreakId = 0; + DebugFlashStyleColorTime = 0.0f; + DebugFlashStyleColorIdx = ImGuiCol_COUNT; + DebugHoveredDockNode = NULL; + + // Same as DebugBreakClearData(). Those fields are scattered in their respective subsystem to stay in hot-data locations + DebugBreakInWindow = 0; + DebugBreakInTable = 0; + DebugBreakInLocateId = false; + DebugBreakKeyChord = ImGuiKey_Pause; + DebugBreakInShortcutRouting = ImGuiKey_None; + + memset(FramerateSecPerFrame, 0, sizeof(FramerateSecPerFrame)); + FramerateSecPerFrameIdx = FramerateSecPerFrameCount = 0; + FramerateSecPerFrameAccum = 0.0f; + WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1; + memset(TempKeychordName, 0, sizeof(TempKeychordName)); +} + void ImGui::Initialize() { ImGuiContext& g = *GImGui; @@ -3691,17 +4189,22 @@ void ImGui::Initialize() // Setup default localization table LocalizeRegisterEntries(GLocalizationEntriesEnUS, IM_ARRAYSIZE(GLocalizationEntriesEnUS)); - // Setup default platform clipboard/IME handlers. - g.IO.GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations - g.IO.SetClipboardTextFn = SetClipboardTextFn_DefaultImpl; - g.IO.ClipboardUserData = (void*)&g; // Default implementation use the ImGuiContext as user data (ideally those would be arguments to the function) - g.IO.SetPlatformImeDataFn = SetPlatformImeDataFn_DefaultImpl; + // Setup default ImGuiPlatformIO clipboard/IME handlers. + g.PlatformIO.Platform_GetClipboardTextFn = Platform_GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations + g.PlatformIO.Platform_SetClipboardTextFn = Platform_SetClipboardTextFn_DefaultImpl; + g.PlatformIO.Platform_OpenInShellFn = Platform_OpenInShellFn_DefaultImpl; + g.PlatformIO.Platform_SetImeDataFn = Platform_SetImeDataFn_DefaultImpl; // Create default viewport ImGuiViewportP* viewport = IM_NEW(ImGuiViewportP)(); viewport->ID = IMGUI_VIEWPORT_DEFAULT_ID; + viewport->Idx = 0; + viewport->PlatformWindowCreated = true; + viewport->Flags = ImGuiViewportFlags_OwnedByApp; g.Viewports.push_back(viewport); g.TempBuffer.resize(1024 * 3 + 1, 0); + g.ViewportCreatedCount++; + g.PlatformIO.Viewports.push_back(g.Viewports[0]); // Build KeysMayBeCharInput[] lookup table (1 bool per named key) for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) @@ -3712,6 +4215,8 @@ void ImGui::Initialize() g.KeysMayBeCharInput.SetBit(key); #ifdef IMGUI_HAS_DOCK + // Initialize Docking + DockContextInitialize(&g); #endif g.Initialized = true; @@ -3741,6 +4246,12 @@ void ImGui::Shutdown() if (g.SettingsLoaded && g.IO.IniFilename != NULL) SaveIniSettingsToDisk(g.IO.IniFilename); + // Destroy platform windows + DestroyPlatformWindows(); + + // Shutdown extensions + DockContextShutdown(&g); + CallContextHooks(&g, ImGuiContextHookType_Shutdown); // Clear everything else @@ -3762,8 +4273,9 @@ void ImGui::Shutdown() g.FontStack.clear(); g.OpenPopupStack.clear(); g.BeginPopupStack.clear(); - g.NavTreeNodeStack.clear(); + g.TreeNodeStack.clear(); + g.CurrentViewport = g.MouseViewport = g.MouseLastHoveredViewport = NULL; g.Viewports.clear_delete(); g.TabBars.Clear(); @@ -3776,6 +4288,9 @@ void ImGui::Shutdown() g.TablesTempData.clear_destruct(); g.DrawChannelsTempMergeBuffer.clear(); + g.MultiSelectStorage.Clear(); + g.MultiSelectTempData.clear_destruct(); + g.ClipboardHandlerData.clear(); g.MenusIdSubmittedThisFrame.clear(); g.InputTextState.ClearFreeMemory(); @@ -3843,21 +4358,27 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* ctx, const char* name) : DrawListInst(NUL NameBufLen = (int)strlen(name) + 1; ID = ImHashStr(name); IDStack.push_back(ID); + ViewportAllowPlatformMonitorExtend = -1; + ViewportPos = ImVec2(FLT_MAX, FLT_MAX); MoveId = GetID("#MOVE"); + TabId = GetID("#TAB"); ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f); AutoFitFramesX = AutoFitFramesY = -1; AutoPosLastDirection = ImGuiDir_None; - SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = 0; + SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = SetWindowDockAllowFlags = 0; SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX); LastFrameActive = -1; + LastFrameJustFocused = -1; LastTimeActive = -1.0f; - FontWindowScale = 1.0f; + FontWindowScale = FontDpiScale = 1.0f; SettingsOffset = -1; + DockOrder = -1; DrawList = &DrawListInst; - DrawList->_Data = &Ctx->DrawListSharedData; DrawList->_OwnerName = Name; + DrawList->_Data = &Ctx->DrawListSharedData; NavPreferredScoringPosRel[0] = NavPreferredScoringPosRel[1] = ImVec2(FLT_MAX, FLT_MAX); + IM_PLACEMENT_NEW(&WindowClass) ImGuiWindowClass(); } ImGuiWindow::~ImGuiWindow() @@ -3871,11 +4392,12 @@ static void SetCurrentWindow(ImGuiWindow* window) { ImGuiContext& g = *GImGui; g.CurrentWindow = window; + g.StackSizesInBeginForCurrentWindow = g.CurrentWindow ? &g.CurrentWindowStack.back().StackSizesInBegin : NULL; g.CurrentTable = window && window->DC.CurrentTableIdx != -1 ? g.Tables.GetByIndex(window->DC.CurrentTableIdx) : NULL; - g.CurrentDpiScale = 1.0f; // FIXME-DPI: WIP this is modified in docking if (window) { g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); + g.FontScale = g.DrawListSharedData.FontScale = g.FontSize / g.Font->FontSize; ImGui::NavUpdateCurrentWindowIsScrollPushableX(); } } @@ -3885,6 +4407,8 @@ void ImGui::GcCompactTransientMiscBuffers() ImGuiContext& g = *GImGui; g.ItemFlagsStack.clear(); g.GroupStack.clear(); + g.MultiSelectTempDataStacked = 0; + g.MultiSelectTempData.clear_destruct(); TableGcCompactSettings(); } @@ -3969,9 +4493,6 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) // (Please note that this is WIP and not all keys/inputs are thoroughly declared by all widgets yet) g.ActiveIdUsingNavDirMask = 0x00; g.ActiveIdUsingAllKeyboardKeys = false; -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - g.ActiveIdUsingNavInputMask = 0x00; -#endif } void ImGui::ClearActiveID() @@ -3996,10 +4517,10 @@ ImGuiID ImGui::GetHoveredID() void ImGui::MarkItemEdited(ImGuiID id) { - // This marking is solely to be able to provide info for IsItemDeactivatedAfterEdit(). + // This marking is to be able to provide info for IsItemDeactivatedAfterEdit(). // ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need to fill the data. ImGuiContext& g = *GImGui; - if (g.LockMarkEdited > 0) + if (g.LastItemData.ItemFlags & ImGuiItemFlags_NoMarkEdited) return; if (g.ActiveId == id || g.ActiveId == 0) { @@ -4009,7 +4530,7 @@ void ImGui::MarkItemEdited(ImGuiID id) // We accept a MarkItemEdited() on drag and drop targets (see https://github.com/ocornut/imgui/issues/1875#issuecomment-978243343) // We accept 'ActiveIdPreviousFrame == id' for InputText() returning an edit after it has been taken ActiveId away (#4714) - IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.ActiveIdPreviousFrame == id); + IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.ActiveIdPreviousFrame == id || (g.CurrentMultiSelect != NULL && g.BoxSelectState.IsActive)); //IM_ASSERT(g.CurrentWindow->DC.LastItemId == id); g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited; @@ -4021,8 +4542,8 @@ bool ImGui::IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flag // FIXME-OPT: This could be cached/stored within the window. ImGuiContext& g = *GImGui; if (g.NavWindow) - if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow) - if (focused_root_window->WasActive && focused_root_window != window->RootWindow) + if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindowDockTree) + if (focused_root_window->WasActive && focused_root_window != window->RootWindowDockTree) { // For the purpose of those flags we differentiate "standard popup" from "modal popup" // NB: The 'else' is important because Modal windows are also Popups. @@ -4037,6 +4558,12 @@ bool ImGui::IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flag if (!IsWindowWithinBeginStackOf(window->RootWindow, focused_root_window)) return false; } + + // Filter by viewport + if (window->Viewport != g.MouseViewport) + if (g.MovingWindow == NULL || window->RootWindowDockTree != g.MovingWindow->RootWindowDockTree) + return false; + return true; } @@ -4065,14 +4592,14 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - IM_ASSERT((flags & ~ImGuiHoveredFlags_AllowedMaskForIsItemHovered) == 0 && "Invalid flags for IsItemHovered()!"); + IM_ASSERT_USER_ERROR((flags & ~ImGuiHoveredFlags_AllowedMaskForIsItemHovered) == 0, "Invalid flags for IsItemHovered()!"); - if (g.NavDisableMouseHover && !g.NavDisableHighlight && !(flags & ImGuiHoveredFlags_NoNavOverride)) + if (g.NavHighlightItemUnderNav && g.NavCursorVisible && !(flags & ImGuiHoveredFlags_NoNavOverride)) { - if ((g.LastItemData.InFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) - return false; if (!IsItemFocused()) return false; + if ((g.LastItemData.ItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) + return false; if (flags & ImGuiHoveredFlags_ForTooltip) flags = ApplyHoverFlagsForTooltip(flags, g.Style.HoverFlagsForTooltipNav); @@ -4087,8 +4614,6 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) if (flags & ImGuiHoveredFlags_ForTooltip) flags = ApplyHoverFlagsForTooltip(flags, g.Style.HoverFlagsForTooltipMouse); - IM_ASSERT((flags & (ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_NoPopupHierarchy)) == 0); // Flags not supported by this function - // Done with rectangle culling so we can perform heavier checks now // Test if we are hovering the right window (our window could be behind another window) // [2021/03/02] Reworked / reverted the revert, finally. Note we want e.g. BeginGroup/ItemAdd/EndGroup to work as well. (#3851) @@ -4102,25 +4627,27 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) // Test if another item is active (e.g. being dragged) const ImGuiID id = g.LastItemData.ID; if ((flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) == 0) - if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId) - return false; + if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap) + if (g.ActiveId != window->MoveId && g.ActiveId != window->TabId) + return false; // Test if interactions on this window are blocked by an active popup or modal. // The ImGuiHoveredFlags_AllowWhenBlockedByPopup flag will be tested here. - if (!IsWindowContentHoverable(window, flags) && !(g.LastItemData.InFlags & ImGuiItemFlags_NoWindowHoverableCheck)) + if (!IsWindowContentHoverable(window, flags) && !(g.LastItemData.ItemFlags & ImGuiItemFlags_NoWindowHoverableCheck)) return false; // Test if the item is disabled - if ((g.LastItemData.InFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) + if ((g.LastItemData.ItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) return false; // Special handling for calling after Begin() which represent the title bar or tab. - // When the window is skipped/collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the case. + // When the window is skipped/collapsed (SkipItems==true) that last item (always ->MoveId submitted by Begin) + // will never be overwritten so we need to detect the case. if (id == window->MoveId && window->WriteAccessed) return false; // Test if using AllowOverlap and overlapped - if ((g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap) && id != 0) + if ((g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap) && id != 0) if ((flags & ImGuiHoveredFlags_AllowWhenOverlappedByItem) == 0) if (g.HoveredIdPreviousFrame != g.LastItemData.ID) return false; @@ -4131,7 +4658,7 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) const float delay = CalcDelayFromHoveredFlags(flags); if (delay > 0.0f || (flags & ImGuiHoveredFlags_Stationary)) { - ImGuiID hover_delay_id = (g.LastItemData.ID != 0) ? g.LastItemData.ID : window->GetIDFromRectangle(g.LastItemData.Rect); + ImGuiID hover_delay_id = (g.LastItemData.ID != 0) ? g.LastItemData.ID : window->GetIDFromPos(g.LastItemData.Rect.Min); if ((flags & ImGuiHoveredFlags_NoSharedDelay) && (g.HoverItemDelayIdPreviousFrame != hover_delay_id)) g.HoverItemDelayTimer = 0.0f; g.HoverItemDelayId = hover_delay_id; @@ -4153,12 +4680,23 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) // (this does not rely on LastItemData it can be called from a ButtonBehavior() call not following an ItemAdd() call) // FIXME-LEGACY: the 'ImGuiItemFlags item_flags' parameter was added on 2023-06-28. // If you used this in your legacy/custom widgets code: -// - Commonly: if your ItemHoverable() call comes after an ItemAdd() call: pass 'item_flags = g.LastItemData.InFlags'. +// - Commonly: if your ItemHoverable() call comes after an ItemAdd() call: pass 'item_flags = g.LastItemData.ItemFlags'. // - Rare: otherwise you may pass 'item_flags = 0' (ImGuiItemFlags_None) unless you want to benefit from special behavior handled by ItemHoverable. bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + + // Detect ID conflicts +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + if (id != 0 && g.HoveredIdPreviousFrame == id && (item_flags & ImGuiItemFlags_AllowDuplicateId) == 0) + { + g.HoveredIdPreviousFrameItemCount++; + if (g.DebugDrawIdConflicts == id) + window->DrawList->AddRect(bb.Min - ImVec2(1,1), bb.Max + ImVec2(1,1), IM_COL32(255, 0, 0, 255), 0.0f, ImDrawFlags_None, 2.0f); + } +#endif + if (g.HoveredWindow != window) return false; if (!IsMouseHoveringRect(bb.Min, bb.Max)) @@ -4197,7 +4735,8 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag } // Display shortcut (only works with mouse) - if (id == g.LastItemData.ID && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasShortcut)) + // (ImGuiItemStatusFlags_HasShortcut in LastItemData denotes we want a tooltip) + if (id == g.LastItemData.ID && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasShortcut) && g.ActiveId != id) if (IsItemHovered(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_DelayNormal)) SetTooltip("%s", GetKeyChordName(g.LastItemData.Shortcut)); } @@ -4226,7 +4765,7 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag } #endif - if (g.NavDisableMouseHover) + if (g.NavHighlightItemUnderNav && (item_flags & ImGuiItemFlags_NoNavDisableMouseHover) == 0) return false; return true; @@ -4251,7 +4790,7 @@ void ImGui::SetLastItemData(ImGuiID item_id, ImGuiItemFlags in_flags, ImGuiItemS { ImGuiContext& g = *GImGui; g.LastItemData.ID = item_id; - g.LastItemData.InFlags = in_flags; + g.LastItemData.ItemFlags = in_flags; g.LastItemData.StatusFlags = item_flags; g.LastItemData.Rect = g.LastItemData.NavRect = item_rect; } @@ -4316,29 +4855,29 @@ void ImGui::DebugAllocHook(ImGuiDebugAllocInfo* info, int frame_count, void* ptr } if (size != (size_t)-1) { + //printf("[%05d] MemAlloc(%d) -> 0x%p\n", frame_count, (int)size, ptr); entry->AllocCount++; info->TotalAllocCount++; - //printf("[%05d] MemAlloc(%d) -> 0x%p\n", frame_count, size, ptr); } else { + //printf("[%05d] MemFree(0x%p)\n", frame_count, ptr); entry->FreeCount++; info->TotalFreeCount++; - //printf("[%05d] MemFree(0x%p)\n", frame_count, ptr); } } const char* ImGui::GetClipboardText() { ImGuiContext& g = *GImGui; - return g.IO.GetClipboardTextFn ? g.IO.GetClipboardTextFn(g.IO.ClipboardUserData) : ""; + return g.PlatformIO.Platform_GetClipboardTextFn ? g.PlatformIO.Platform_GetClipboardTextFn(&g) : ""; } void ImGui::SetClipboardText(const char* text) { ImGuiContext& g = *GImGui; - if (g.IO.SetClipboardTextFn) - g.IO.SetClipboardTextFn(g.IO.ClipboardUserData, text); + if (g.PlatformIO.Platform_SetClipboardTextFn != NULL) + g.PlatformIO.Platform_SetClipboardTextFn(&g, text); } const char* ImGui::GetVersion() @@ -4352,6 +4891,26 @@ ImGuiIO& ImGui::GetIO() return GImGui->IO; } +// This variant exists to facilitate backends experimenting with multi-threaded parallel context. (#8069, #6293, #5856) +ImGuiIO& ImGui::GetIOEx(ImGuiContext* ctx) +{ + IM_ASSERT(ctx != NULL); + return ctx->IO; +} + +ImGuiPlatformIO& ImGui::GetPlatformIO() +{ + IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext()?"); + return GImGui->PlatformIO; +} + +// This variant exists to facilitate backends experimenting with multi-threaded parallel context. (#8069, #6293, #5856) +ImGuiPlatformIO& ImGui::GetPlatformIOEx(ImGuiContext* ctx) +{ + IM_ASSERT(ctx != NULL); + return ctx->PlatformIO; +} + // Pass this to your backend rendering function! Valid after Render() and until the next call to NewFrame() ImDrawData* ImGui::GetDrawData() { @@ -4396,26 +4955,18 @@ static ImDrawList* GetViewportBgFgDrawList(ImGuiViewportP* viewport, size_t draw ImDrawList* ImGui::GetBackgroundDrawList(ImGuiViewport* viewport) { + if (viewport == NULL) + viewport = GImGui->CurrentWindow->Viewport; return GetViewportBgFgDrawList((ImGuiViewportP*)viewport, 0, "##Background"); } -ImDrawList* ImGui::GetBackgroundDrawList() -{ - ImGuiContext& g = *GImGui; - return GetBackgroundDrawList(g.Viewports[0]); -} - ImDrawList* ImGui::GetForegroundDrawList(ImGuiViewport* viewport) { + if (viewport == NULL) + viewport = GImGui->CurrentWindow->Viewport; return GetViewportBgFgDrawList((ImGuiViewportP*)viewport, 1, "##Foreground"); } -ImDrawList* ImGui::GetForegroundDrawList() -{ - ImGuiContext& g = *GImGui; - return GetForegroundDrawList(g.Viewports[0]); -} - ImDrawListSharedData* ImGui::GetDrawListSharedData() { return &GImGui->DrawListSharedData; @@ -4429,18 +4980,45 @@ void ImGui::StartMouseMovingWindow(ImGuiWindow* window) ImGuiContext& g = *GImGui; FocusWindow(window); SetActiveID(window->MoveId, window); - g.NavDisableHighlight = true; - g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window->RootWindow->Pos; + if (g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = false; + g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window->RootWindowDockTree->Pos; g.ActiveIdNoClearOnFocusLoss = true; SetActiveIdUsingAllKeyboardKeys(); bool can_move_window = true; - if ((window->Flags & ImGuiWindowFlags_NoMove) || (window->RootWindow->Flags & ImGuiWindowFlags_NoMove)) + if ((window->Flags & ImGuiWindowFlags_NoMove) || (window->RootWindowDockTree->Flags & ImGuiWindowFlags_NoMove)) can_move_window = false; + if (ImGuiDockNode* node = window->DockNodeAsHost) + if (node->VisibleWindow && (node->VisibleWindow->Flags & ImGuiWindowFlags_NoMove)) + can_move_window = false; if (can_move_window) g.MovingWindow = window; } +// We use 'undock == false' when dragging from title bar to allow moving groups of floating nodes without undocking them. +void ImGui::StartMouseMovingWindowOrNode(ImGuiWindow* window, ImGuiDockNode* node, bool undock) +{ + ImGuiContext& g = *GImGui; + bool can_undock_node = false; + if (undock && node != NULL && node->VisibleWindow && (node->VisibleWindow->Flags & ImGuiWindowFlags_NoMove) == 0 && (node->MergedFlags & ImGuiDockNodeFlags_NoUndocking) == 0) + { + // Can undock if: + // - part of a hierarchy with more than one visible node (if only one is visible, we'll just move the root window) + // - part of a dockspace node hierarchy: so we can undock the last single visible node too. Undocking from a fixed/central node will create a new node and copy windows. + ImGuiDockNode* root_node = DockNodeGetRootNode(node); + if (root_node->OnlyNodeWithWindows != node || root_node->CentralNode != NULL) // -V1051 PVS-Studio thinks node should be root_node and is wrong about that. + can_undock_node = true; + } + + const bool clicked = IsMouseClicked(0); + const bool dragging = IsMouseDragging(0); + if (can_undock_node && dragging) + DockContextQueueUndockNode(&g, node); // Will lead to DockNodeStartMouseMovingWindow() -> StartMouseMovingWindow() being called next frame + else if (!can_undock_node && (clicked || dragging) && g.MovingWindow != window) + StartMouseMovingWindow(window); +} + // Handle mouse moving window // Note: moving window with the navigation keys (Square + d-pad / CTRL+TAB + Arrows) are processed in NavUpdateWindowing() // FIXME: We don't have strong guarantee that g.MovingWindow stay synched with g.ActiveId == g.MovingWindow->MoveId. @@ -4454,16 +5032,43 @@ void ImGui::UpdateMouseMovingWindowNewFrame() // We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window). // We track it to preserve Focus and so that generally ActiveIdWindow == MovingWindow and ActiveId == MovingWindow->MoveId for consistency. KeepAliveID(g.ActiveId); - IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow); - ImGuiWindow* moving_window = g.MovingWindow->RootWindow; - if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos)) + IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindowDockTree); + ImGuiWindow* moving_window = g.MovingWindow->RootWindowDockTree; + + // When a window stop being submitted while being dragged, it may will its viewport until next Begin() + const bool window_disappared = (!moving_window->WasActive && !moving_window->Active); + if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos) && !window_disappared) { ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset; - SetWindowPos(moving_window, pos, ImGuiCond_Always); + if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y) + { + SetWindowPos(moving_window, pos, ImGuiCond_Always); + if (moving_window->Viewport && moving_window->ViewportOwned) // Synchronize viewport immediately because some overlays may relies on clipping rectangle before we Begin() into the window. + { + moving_window->Viewport->Pos = pos; + moving_window->Viewport->UpdateWorkRect(); + } + } FocusWindow(g.MovingWindow); } else { + if (!window_disappared) + { + // Try to merge the window back into the main viewport. + // This works because MouseViewport should be != MovingWindow->Viewport on release (as per code in UpdateViewports) + if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) + UpdateTryMergeWindowIntoHostViewport(moving_window, g.MouseViewport); + + // Restore the mouse viewport so that we don't hover the viewport _under_ the moved window during the frame we released the mouse button. + if (moving_window->Viewport && !IsDragDropPayloadBeingAccepted()) + g.MouseViewport = moving_window->Viewport; + + // Clear the NoInput window flag set by the Viewport system + if (moving_window->Viewport) + moving_window->Viewport->Flags &= ~ImGuiViewportFlags_NoInputs; + } + g.MovingWindow = NULL; ClearActiveID(); } @@ -4480,12 +5085,13 @@ void ImGui::UpdateMouseMovingWindowNewFrame() } } -// Initiate moving window when clicking on empty space or title bar. +// Initiate focusing and moving window when clicking on empty space or title bar. +// Initiate focusing window when clicking on a disabled item. // Handle left-click and right-click focus. void ImGui::UpdateMouseMovingWindowEndFrame() { ImGuiContext& g = *GImGui; - if (g.ActiveId != 0 || g.HoveredId != 0) + if (g.ActiveId != 0 || (g.HoveredId != 0 && !g.HoveredIdIsDisabled)) return; // Unless we just made a window/popup appear @@ -4493,7 +5099,7 @@ void ImGui::UpdateMouseMovingWindowEndFrame() return; // Click on empty space to focus window and start moving - // (after we're done with all our widgets) + // (after we're done with all our widgets, so e.g. clicking on docking tab-bar which have set HoveredId already and not get us here!) if (g.IO.MouseClicked[0]) { // Handle the edge case of a popup being closed while clicking in its empty space. @@ -4506,11 +5112,13 @@ void ImGui::UpdateMouseMovingWindowEndFrame() StartMouseMovingWindow(g.HoveredWindow); //-V595 // Cancel moving if clicked outside of title bar - if (g.IO.ConfigWindowsMoveFromTitleBarOnly && !(root_window->Flags & ImGuiWindowFlags_NoTitleBar)) - if (!root_window->TitleBarRect().Contains(g.IO.MouseClickedPos[0])) - g.MovingWindow = NULL; + if (g.IO.ConfigWindowsMoveFromTitleBarOnly) + if (!(root_window->Flags & ImGuiWindowFlags_NoTitleBar) || root_window->DockIsActive) + if (!root_window->TitleBarRect().Contains(g.IO.MouseClickedPos[0])) + g.MovingWindow = NULL; - // Cancel moving if clicked over an item which was disabled or inhibited by popups (note that we know HoveredId == 0 already) + // Cancel moving if clicked over an item which was disabled or inhibited by popups + // (when g.HoveredIdIsDisabled == true && g.HoveredId == 0 we are inhibited by popups, when g.HoveredIdIsDisabled == true && g.HoveredId != 0 we are over a disabled item)0 already) if (g.HoveredIdIsDisabled) g.MovingWindow = NULL; } @@ -4524,7 +5132,7 @@ void ImGui::UpdateMouseMovingWindowEndFrame() // With right mouse button we close popups without changing focus based on where the mouse is aimed // Instead, focus will be restored to the window under the bottom-most closed popup. // (The left mouse button path calls FocusWindow on the hovered window, which will lead NewFrame->ClosePopupsOverWindow to trigger) - if (g.IO.MouseClicked[1]) + if (g.IO.MouseClicked[1] && g.HoveredId == 0) { // Find the top-most window between HoveredWindow and the top-most Modal Window. // This is where we can trim the popup stack. @@ -4534,6 +5142,29 @@ void ImGui::UpdateMouseMovingWindowEndFrame() } } +// This is called during NewFrame()->UpdateViewportsNewFrame() only. +// Need to keep in sync with SetWindowPos() +static void TranslateWindow(ImGuiWindow* window, const ImVec2& delta) +{ + window->Pos += delta; + window->ClipRect.Translate(delta); + window->OuterRectClipped.Translate(delta); + window->InnerRect.Translate(delta); + window->DC.CursorPos += delta; + window->DC.CursorStartPos += delta; + window->DC.CursorMaxPos += delta; + window->DC.IdealMaxPos += delta; +} + +static void ScaleWindow(ImGuiWindow* window, float scale) +{ + ImVec2 origin = window->Viewport->Pos; + window->Pos = ImFloor((window->Pos - origin) * scale + origin); + window->Size = ImTrunc(window->Size * scale); + window->SizeFull = ImTrunc(window->SizeFull * scale); + window->ContentSize = ImTrunc(window->ContentSize * scale); +} + static bool IsWindowActiveAndVisible(ImGuiWindow* window) { return (window->Active) && (!window->Hidden); @@ -4555,13 +5186,15 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() // - We also support the moved window toggling the NoInputs flag after moving has started in order to be able to detect windows below it, which is useful for e.g. docking mechanisms. bool clear_hovered_windows = false; FindHoveredWindowEx(g.IO.MousePos, false, &g.HoveredWindow, &g.HoveredWindowUnderMovingWindow); + IM_ASSERT(g.HoveredWindow == NULL || g.HoveredWindow == g.MovingWindow || g.HoveredWindow->Viewport == g.MouseViewport); + g.HoveredWindowBeforeClear = g.HoveredWindow; // Modal windows prevents mouse from hovering behind them. ImGuiWindow* modal_window = GetTopMostPopupModal(); - if (modal_window && g.HoveredWindow && !IsWindowWithinBeginStackOf(g.HoveredWindow->RootWindow, modal_window)) + if (modal_window && g.HoveredWindow && !IsWindowWithinBeginStackOf(g.HoveredWindow->RootWindow, modal_window)) // FIXME-MERGE: RootWindowDockTree ? clear_hovered_windows = true; - // Disabled mouse? + // Disabled mouse hovering (we don't currently clear MousePos, we could) if (io.ConfigFlags & ImGuiConfigFlags_NoMouse) clear_hovered_windows = true; @@ -4579,7 +5212,7 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() io.MouseDownOwnedUnlessPopupClose[i] = (g.HoveredWindow != NULL) || has_open_modal; } mouse_any_down |= io.MouseDown[i]; - if (io.MouseDown[i]) + if (io.MouseDown[i] || io.MouseReleased[i]) // Increase release frame for our evaluation of earliest button (#1392) if (mouse_earliest_down == -1 || io.MouseClickedTime[i] < io.MouseClickedTime[mouse_earliest_down]) mouse_earliest_down = i; } @@ -4608,9 +5241,14 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() } // Update io.WantCaptureKeyboard for the user application (true = dispatch keyboard info to Dear ImGui only, false = dispatch keyboard info to Dear ImGui + underlying app) - io.WantCaptureKeyboard = (g.ActiveId != 0) || (modal_window != NULL); - if (io.NavActive && (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && !(io.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard)) - io.WantCaptureKeyboard = true; + io.WantCaptureKeyboard = false; + if ((io.ConfigFlags & ImGuiConfigFlags_NoKeyboard) == 0) + { + if ((g.ActiveId != 0) || (modal_window != NULL)) + io.WantCaptureKeyboard = true; + else if (io.NavActive && (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && io.ConfigNavCaptureKeyboard) + io.WantCaptureKeyboard = true; + } if (g.WantCaptureKeyboardNextFrame != -1) // Manual override io.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0); @@ -4618,7 +5256,7 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() io.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false; } -// Calling SetupDrawListSharedData() is followed by SetCurrentFont() which sets up the remaining data. +// Called once a frame. Followed by SetCurrentFont() which sets up the remaining data. static void SetupDrawListSharedData() { ImGuiContext& g = *GImGui; @@ -4653,7 +5291,9 @@ void ImGui::NewFrame() CallContextHooks(&g, ImGuiContextHookType_NewFramePre); // Check and assert for various common IO and Configuration mistakes + g.ConfigFlagsLastFrame = g.ConfigFlagsCurrFrame; ErrorCheckNewFrameSanityChecks(); + g.ConfigFlagsCurrFrame = g.IO.ConfigFlags; // Load settings on first frame, save settings when modified (after a delay) UpdateSettings(); @@ -4680,6 +5320,7 @@ void ImGui::NewFrame() UpdateViewportsNewFrame(); // Setup current font and draw list shared data + // FIXME-VIEWPORT: the concept of a single ClipRectFullscreen is not ideal! g.IO.Fonts->Locked = true; SetupDrawListSharedData(); SetCurrentFont(GetDefaultFont()); @@ -4687,12 +5328,21 @@ void ImGui::NewFrame() // Mark rendering data as invalid to prevent user who may have a handle on it to use it. for (ImGuiViewportP* viewport : g.Viewports) + { + viewport->DrawData = NULL; viewport->DrawDataP.Valid = false; + } // Drag and drop keep the source ID alive so even if the source disappear our state is consistent if (g.DragDropActive && g.DragDropPayload.SourceId == g.ActiveId) KeepAliveID(g.DragDropPayload.SourceId); + // [DEBUG] + if (!g.IO.ConfigDebugHighlightIdConflicts || !g.IO.KeyCtrl) // Count is locked while holding CTRL + g.DebugDrawIdConflicts = 0; + if (g.IO.ConfigDebugHighlightIdConflicts && g.HoveredIdPreviousFrameItemCount > 1) + g.DebugDrawIdConflicts = g.HoveredIdPreviousFrame; + // Update HoveredId data if (!g.HoveredIdPreviousFrame) g.HoveredIdTimer = 0.0f; @@ -4703,6 +5353,7 @@ void ImGui::NewFrame() if (g.HoveredId && g.ActiveId != g.HoveredId) g.HoveredIdNotActiveTimer += g.IO.DeltaTime; g.HoveredIdPreviousFrame = g.HoveredId; + g.HoveredIdPreviousFrameItemCount = 0; g.HoveredId = 0; g.HoveredIdAllowOverlap = false; g.HoveredIdIsDisabled = false; @@ -4733,24 +5384,7 @@ void ImGui::NewFrame() { g.ActiveIdUsingNavDirMask = 0x00; g.ActiveIdUsingAllKeyboardKeys = false; -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - g.ActiveIdUsingNavInputMask = 0x00; -#endif - } - -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - if (g.ActiveId == 0) - g.ActiveIdUsingNavInputMask = 0; - else if (g.ActiveIdUsingNavInputMask != 0) - { - // If your custom widget code used: { g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel); } - // Since IMGUI_VERSION_NUM >= 18804 it should be: { SetKeyOwner(ImGuiKey_Escape, g.ActiveId); SetKeyOwner(ImGuiKey_NavGamepadCancel, g.ActiveId); } - if (g.ActiveIdUsingNavInputMask & (1 << ImGuiNavInput_Cancel)) - SetKeyOwner(ImGuiKey_Escape, g.ActiveId); - if (g.ActiveIdUsingNavInputMask & ~(1 << ImGuiNavInput_Cancel)) - IM_ASSERT(0); // Other values unsupported } -#endif // Record when we have been stationary as this state is preserved while over same item. // FIXME: The way this is expressed means user cannot alter HoverStationaryDelay during the frame to use varying values. @@ -4788,6 +5422,7 @@ void ImGui::NewFrame() g.DragDropWithinSource = false; g.DragDropWithinTarget = false; g.DragDropHoldJustPressedId = 0; + g.TooltipPreviousWindow = NULL; // Close popups on focus lost (currently wip/opt-in) //if (g.IO.AppFocusLost) @@ -4801,14 +5436,35 @@ void ImGui::NewFrame() //IM_ASSERT(g.IO.KeyAlt == IsKeyDown(ImGuiKey_LeftAlt) || IsKeyDown(ImGuiKey_RightAlt)); //IM_ASSERT(g.IO.KeySuper == IsKeyDown(ImGuiKey_LeftSuper) || IsKeyDown(ImGuiKey_RightSuper)); - // Update gamepad/keyboard navigation + // Update keyboard/gamepad navigation NavUpdate(); // Update mouse input state UpdateMouseInputs(); + // Undocking + // (needs to be before UpdateMouseMovingWindowNewFrame so the window is already offset and following the mouse on the detaching frame) + DockContextNewFrameUpdateUndocking(&g); + + // Mark all windows as not visible and compact unused memory. + IM_ASSERT(g.WindowsFocusOrder.Size <= g.Windows.Size); + const float memory_compact_start_time = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f) ? FLT_MAX : (float)g.Time - g.IO.ConfigMemoryCompactTimer; + for (ImGuiWindow* window : g.Windows) + { + window->WasActive = window->Active; + window->Active = false; + window->WriteAccessed = false; + window->BeginCountPreviousFrame = window->BeginCount; + window->BeginCount = 0; + + // Garbage collect transient buffers of recently unused windows + if (!window->WasActive && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time) + GcCompactTransientWindowBuffers(window); + } + // Find hovered window // (needs to be before UpdateMouseMovingWindowNewFrame so we fill g.HoveredWindowUnderMovingWindow on the mouse release frame) + // (currently needs to be done after the WasActive=Active loop and FindHoveredWindowEx uses ->Active) UpdateHoveredWindowAndCaptureFlags(); // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering) @@ -4830,22 +5486,6 @@ void ImGui::NewFrame() // Mouse wheel scrolling, scale UpdateMouseWheel(); - // Mark all windows as not visible and compact unused memory. - IM_ASSERT(g.WindowsFocusOrder.Size <= g.Windows.Size); - const float memory_compact_start_time = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f) ? FLT_MAX : (float)g.Time - g.IO.ConfigMemoryCompactTimer; - for (ImGuiWindow* window : g.Windows) - { - window->WasActive = window->Active; - window->Active = false; - window->WriteAccessed = false; - window->BeginCountPreviousFrame = window->BeginCount; - window->BeginCount = 0; - - // Garbage collect transient buffers of recently unused windows - if (!window->WasActive && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time) - GcCompactTransientWindowBuffers(window); - } - // Garbage collect transient buffers of recently unused tables for (int i = 0; i < g.TablesLastTimeActive.Size; i++) if (g.TablesLastTimeActive[i] >= 0.0f && g.TablesLastTimeActive[i] < memory_compact_start_time) @@ -4866,9 +5506,13 @@ void ImGui::NewFrame() g.CurrentWindowStack.resize(0); g.BeginPopupStack.resize(0); g.ItemFlagsStack.resize(0); - g.ItemFlagsStack.push_back(ImGuiItemFlags_None); + g.ItemFlagsStack.push_back(ImGuiItemFlags_AutoClosePopups); // Default flags + g.CurrentItemFlags = g.ItemFlagsStack.back(); g.GroupStack.resize(0); + // Docking + DockContextNewFrameUpdateDocking(&g); + // [DEBUG] Update debug features #ifndef IMGUI_DISABLE_DEBUG_TOOLS UpdateDebugToolItemPicker(); @@ -4895,6 +5539,10 @@ void ImGui::NewFrame() Begin("Debug##Default"); IM_ASSERT(g.CurrentWindow->IsFallbackWindow == true); + // Store stack sizes + g.ErrorCountCurrentFrame = 0; + ErrorRecoveryStoreState(&g.StackSizesInNewFrame); + // [DEBUG] When io.ConfigDebugBeginReturnValue is set, we make Begin()/BeginChild() return false at different level of the window-stack, // allowing to validate correct Begin/End behavior in user code. #ifndef IMGUI_DISABLE_DEBUG_TOOLS @@ -4938,7 +5586,8 @@ static void AddWindowToSortBuffer(ImVector* out_sorted_windows, Im static void AddWindowToDrawData(ImGuiWindow* window, int layer) { ImGuiContext& g = *GImGui; - ImGuiViewportP* viewport = g.Viewports[0]; + ImGuiViewportP* viewport = window->Viewport; + IM_ASSERT(viewport != NULL); g.IO.MetricsRenderWindows++; if (window->DrawList->_Splitter._Count > 1) window->DrawList->ChannelsMerge(); // Merge if user forgot to merge back. Also required in Docking branch for ImGuiWindowFlags_DockNodeHost windows. @@ -4982,17 +5631,25 @@ static void InitViewportDrawData(ImGuiViewportP* viewport) ImGuiIO& io = ImGui::GetIO(); ImDrawData* draw_data = &viewport->DrawDataP; + viewport->DrawData = draw_data; // Make publicly accessible viewport->DrawDataBuilder.Layers[0] = &draw_data->CmdLists; viewport->DrawDataBuilder.Layers[1] = &viewport->DrawDataBuilder.LayerData1; viewport->DrawDataBuilder.Layers[0]->resize(0); viewport->DrawDataBuilder.Layers[1]->resize(0); + // When minimized, we report draw_data->DisplaySize as zero to be consistent with non-viewport mode, + // and to allow applications/backends to easily skip rendering. + // FIXME: Note that we however do NOT attempt to report "zero drawlist / vertices" into the ImDrawData structure. + // This is because the work has been done already, and its wasted! We should fix that and add optimizations for + // it earlier in the pipeline, rather than pretend to hide the data at the end of the pipeline. + const bool is_minimized = (viewport->Flags & ImGuiViewportFlags_IsMinimized) != 0; + draw_data->Valid = true; draw_data->CmdListsCount = 0; draw_data->TotalVtxCount = draw_data->TotalIdxCount = 0; draw_data->DisplayPos = viewport->Pos; - draw_data->DisplaySize = viewport->Size; - draw_data->FramebufferScale = io.DisplayFramebufferScale; + draw_data->DisplaySize = is_minimized ? ImVec2(0.0f, 0.0f) : viewport->Size; + draw_data->FramebufferScale = io.DisplayFramebufferScale; // FIXME-VIEWPORT: This may vary on a per-monitor/viewport basis? draw_data->OwnerViewport = viewport; } @@ -5002,6 +5659,8 @@ static void InitViewportDrawData(ImGuiViewportP* viewport) // - If the code here changes, may need to update code of functions like NextColumn() and PushColumnClipRect(): // some frequently called functions which to modify both channels and clipping simultaneously tend to use the // more specialized SetWindowClipRectBeforeSetChannel() to avoid extraneous updates of underlying ImDrawCmds. +// - This is analoguous to PushFont()/PopFont() in the sense that are a mixing a global stack and a window stack, +// which in the case of ClipRect is not so problematic but tends to be more restrictive for fonts. void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect) { ImGuiWindow* window = GetCurrentWindow(); @@ -5016,20 +5675,28 @@ void ImGui::PopClipRect() window->ClipRect = window->DrawList->_ClipRectStack.back(); } +static ImGuiWindow* FindFrontMostVisibleChildWindow(ImGuiWindow* window) +{ + for (int n = window->DC.ChildWindows.Size - 1; n >= 0; n--) + if (IsWindowActiveAndVisible(window->DC.ChildWindows[n])) + return FindFrontMostVisibleChildWindow(window->DC.ChildWindows[n]); + return window; +} + static void ImGui::RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 col) { if ((col & IM_COL32_A_MASK) == 0) return; - ImGuiViewportP* viewport = (ImGuiViewportP*)GetMainViewport(); + ImGuiViewportP* viewport = window->Viewport; ImRect viewport_rect = viewport->GetMainRect(); // Draw behind window by moving the draw command at the FRONT of the draw list { - // We've already called AddWindowToDrawData() which called DrawList->ChannelsMerge() on DockNodeHost windows, - // and draw list have been trimmed already, hence the explicit recreation of a draw command if missing. + // Draw list have been trimmed already, hence the explicit recreation of a draw command if missing. // FIXME: This is creating complication, might be simpler if we could inject a drawlist in drawdata at a given position and not attempt to manipulate ImDrawCmd order. - ImDrawList* draw_list = window->RootWindow->DrawList; + ImDrawList* draw_list = window->RootWindowDockTree->DrawList; + draw_list->ChannelsMerge(); if (draw_list->CmdBuffer.Size == 0) draw_list->AddDrawCmd(); draw_list->PushClipRect(viewport_rect.Min - ImVec2(1, 1), viewport_rect.Max + ImVec2(1, 1), false); // FIXME: Need to stricty ensure ImDrawCmd are not merged (ElemCount==6 checks below will verify that) @@ -5041,6 +5708,18 @@ static void ImGui::RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 draw_list->AddDrawCmd(); // We need to create a command as CmdBuffer.back().IdxOffset won't be correct if we append to same command. draw_list->PopClipRect(); } + + // Draw over sibling docking nodes in a same docking tree + if (window->RootWindow->DockIsActive) + { + ImDrawList* draw_list = FindFrontMostVisibleChildWindow(window->RootWindowDockTree)->DrawList; + draw_list->ChannelsMerge(); + if (draw_list->CmdBuffer.Size == 0) + draw_list->AddDrawCmd(); + draw_list->PushClipRect(viewport_rect.Min, viewport_rect.Max, false); + RenderRectFilledWithHole(draw_list, window->RootWindowDockTree->Rect(), window->RootWindow->Rect(), col, 0.0f);// window->RootWindowDockTree->WindowRounding); + draw_list->PopClipRect(); + } } ImGuiWindow* ImGui::FindBottomMostVisibleWindowWithinBeginStack(ImGuiWindow* parent_window) @@ -5060,6 +5739,8 @@ ImGuiWindow* ImGui::FindBottomMostVisibleWindowWithinBeginStack(ImGuiWindow* par return bottom_most_visible_window; } +// Important: AddWindowToDrawData() has not been called yet, meaning DockNodeHost windows needs a DrawList->ChannelsMerge() before usage. +// We call ChannelsMerge() lazily here at it is faster that doing a full iteration of g.Windows[] prior to calling RenderDimmedBackgrounds(). static void ImGui::RenderDimmedBackgrounds() { ImGuiContext& g = *GImGui; @@ -5071,31 +5752,50 @@ static void ImGui::RenderDimmedBackgrounds() if (!dim_bg_for_modal && !dim_bg_for_window_list) return; + ImGuiViewport* viewports_already_dimmed[2] = { NULL, NULL }; if (dim_bg_for_modal) { // Draw dimming behind modal or a begin stack child, whichever comes first in draw order. ImGuiWindow* dim_behind_window = FindBottomMostVisibleWindowWithinBeginStack(modal_window); RenderDimmedBackgroundBehindWindow(dim_behind_window, GetColorU32(modal_window->DC.ModalDimBgColor, g.DimBgRatio)); + viewports_already_dimmed[0] = modal_window->Viewport; } else if (dim_bg_for_window_list) { - // Draw dimming behind CTRL+Tab target window + // Draw dimming behind CTRL+Tab target window and behind CTRL+Tab UI window RenderDimmedBackgroundBehindWindow(g.NavWindowingTargetAnim, GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio)); + if (g.NavWindowingListWindow != NULL && g.NavWindowingListWindow->Viewport && g.NavWindowingListWindow->Viewport != g.NavWindowingTargetAnim->Viewport) + RenderDimmedBackgroundBehindWindow(g.NavWindowingListWindow, GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio)); + viewports_already_dimmed[0] = g.NavWindowingTargetAnim->Viewport; + viewports_already_dimmed[1] = g.NavWindowingListWindow ? g.NavWindowingListWindow->Viewport : NULL; // Draw border around CTRL+Tab target window ImGuiWindow* window = g.NavWindowingTargetAnim; - ImGuiViewport* viewport = GetMainViewport(); + ImGuiViewport* viewport = window->Viewport; float distance = g.FontSize; ImRect bb = window->Rect(); bb.Expand(distance); if (bb.GetWidth() >= viewport->Size.x && bb.GetHeight() >= viewport->Size.y) bb.Expand(-distance - 1.0f); // If a window fits the entire viewport, adjust its highlight inward + window->DrawList->ChannelsMerge(); if (window->DrawList->CmdBuffer.Size == 0) window->DrawList->AddDrawCmd(); window->DrawList->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size); window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), window->WindowRounding, 0, 3.0f); window->DrawList->PopClipRect(); } + + // Draw dimming background on _other_ viewports than the ones our windows are in + for (ImGuiViewportP* viewport : g.Viewports) + { + if (viewport == viewports_already_dimmed[0] || viewport == viewports_already_dimmed[1]) + continue; + if (modal_window && viewport->Window && IsWindowAbove(viewport->Window, modal_window)) + continue; + ImDrawList* draw_list = GetForegroundDrawList(viewport); + const ImU32 dim_bg_col = GetColorU32(dim_bg_for_modal ? ImGuiCol_ModalWindowDimBg : ImGuiCol_NavWindowingDimBg, g.DimBgRatio); + draw_list->AddRectFilled(viewport->Pos, viewport->Pos + viewport->Size, dim_bg_col); + } } // This is normally called by Render(). You may want to call it directly if you want to avoid calling Render() but the gain will be very minimal. @@ -5111,15 +5811,21 @@ void ImGui::EndFrame() CallContextHooks(&g, ImGuiContextHookType_EndFramePre); + // [EXPERIMENTAL] Recover from errors + if (g.IO.ConfigErrorRecovery) + ErrorRecoveryTryToRecoverState(&g.StackSizesInNewFrame); ErrorCheckEndFrameSanityChecks(); + ErrorCheckEndFrameFinalizeErrorTooltip(); // Notify Platform/OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME) ImGuiPlatformImeData* ime_data = &g.PlatformImeData; - if (g.IO.SetPlatformImeDataFn && memcmp(ime_data, &g.PlatformImeDataPrev, sizeof(ImGuiPlatformImeData)) != 0) + if (g.PlatformIO.Platform_SetImeDataFn != NULL && memcmp(ime_data, &g.PlatformImeDataPrev, sizeof(ImGuiPlatformImeData)) != 0) { - IMGUI_DEBUG_LOG_IO("[io] Calling io.SetPlatformImeDataFn(): WantVisible: %d, InputPos (%.2f,%.2f)\n", ime_data->WantVisible, ime_data->InputPos.x, ime_data->InputPos.y); - ImGuiViewport* viewport = GetMainViewport(); - g.IO.SetPlatformImeDataFn(viewport, ime_data); + ImGuiViewport* viewport = FindViewportByID(g.PlatformImeViewport); + IMGUI_DEBUG_LOG_IO("[io] Calling Platform_SetImeDataFn(): WantVisible: %d, InputPos (%.2f,%.2f)\n", ime_data->WantVisible, ime_data->InputPos.x, ime_data->InputPos.y); + if (viewport == NULL) + viewport = GetMainViewport(); + g.PlatformIO.Platform_SetImeDataFn(&g, viewport, ime_data); } // Hide implicit/fallback "Debug" window if it hasn't been used @@ -5131,17 +5837,26 @@ void ImGui::EndFrame() // Update navigation: CTRL+Tab, wrap-around requests NavEndFrame(); + // Update docking + DockContextEndFrame(&g); + + SetCurrentViewport(NULL, NULL); + // Drag and Drop: Elapse payload (if delivered, or if source stops being submitted) if (g.DragDropActive) { bool is_delivered = g.DragDropPayload.Delivery; - bool is_elapsed = (g.DragDropPayload.DataFrameCount + 1 < g.FrameCount) && ((g.DragDropSourceFlags & ImGuiDragDropFlags_SourceAutoExpirePayload) || !IsMouseDown(g.DragDropMouseButton)); + bool is_elapsed = (g.DragDropSourceFrameCount + 1 < g.FrameCount) && ((g.DragDropSourceFlags & ImGuiDragDropFlags_PayloadAutoExpire) || g.DragDropMouseButton == -1 || !IsMouseDown(g.DragDropMouseButton)); if (is_delivered || is_elapsed) ClearDragDrop(); } - // Drag and Drop: Fallback for source tooltip. This is not ideal but better than nothing. - if (g.DragDropActive && g.DragDropSourceFrameCount < g.FrameCount && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + // Drag and Drop: Fallback for missing source tooltip. This is not ideal but better than nothing. + // If you want to handle source item disappearing: instead of submitting your description tooltip + // in the BeginDragDropSource() block of the dragged item, you can submit them from a safe single spot + // (e.g. end of your item loop, or before EndFrame) by reading payload data. + // In the typical case, the contents of drag tooltip should be possible to infer solely from payload data. + if (g.DragDropActive && g.DragDropSourceFrameCount + 1 < g.FrameCount && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) { g.DragDropWithinSource = true; SetTooltip("..."); @@ -5155,6 +5870,9 @@ void ImGui::EndFrame() // Initiate moving window + handle left-click and right-click focus UpdateMouseMovingWindowEndFrame(); + // Update user-facing viewport list (g.Viewports -> g.PlatformIO.Viewports after filtering out some) + UpdateViewportsEndFrame(); + // Sort the window list so that all child windows are after their parent // We cannot do that on FocusWindow() because children may not exist yet g.WindowsTempSortBuffer.resize(0); @@ -5200,9 +5918,6 @@ void ImGui::Render() g.IO.MetricsRenderWindows = 0; CallContextHooks(&g, ImGuiContextHookType_RenderPre); - // Draw modal/window whitening backgrounds - RenderDimmedBackgrounds(); - // Add background ImDrawList (for each active viewport) for (ImGuiViewportP* viewport : g.Viewports) { @@ -5211,9 +5926,12 @@ void ImGui::Render() AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[0], GetBackgroundDrawList(viewport)); } + // Draw modal/window whitening backgrounds + RenderDimmedBackgrounds(); + // Add ImDrawList to render ImGuiWindow* windows_to_render_top_most[2]; - windows_to_render_top_most[0] = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget->RootWindow : NULL; + windows_to_render_top_most[0] = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget->RootWindowDockTree : NULL; windows_to_render_top_most[1] = (g.NavWindowingTarget ? g.NavWindowingListWindow : NULL); for (ImGuiWindow* window : g.Windows) { @@ -5292,8 +6010,15 @@ void ImGui::FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_vi ImGuiWindow* hovered_window = NULL; ImGuiWindow* hovered_window_under_moving_window = NULL; - if (find_first_and_in_any_viewport == false && g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoMouseInputs)) - hovered_window = g.MovingWindow; + // Special handling for the window being moved: Ignore the mouse viewport check (because it may reset/lose its viewport during the undocking frame) + ImGuiViewportP* backup_moving_window_viewport = NULL; + if (find_first_and_in_any_viewport == false && g.MovingWindow) + { + backup_moving_window_viewport = g.MovingWindow->Viewport; + g.MovingWindow->Viewport = g.MouseViewport; + if (!(g.MovingWindow->Flags & ImGuiWindowFlags_NoMouseInputs)) + hovered_window = g.MovingWindow; + } ImVec2 padding_regular = g.Style.TouchExtraPadding; ImVec2 padding_for_resize = g.IO.ConfigWindowsResizeFromEdges ? g.WindowsHoverPadding : padding_regular; @@ -5301,10 +6026,13 @@ void ImGui::FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_vi { ImGuiWindow* window = g.Windows[i]; IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer. - if (!window->Active || window->Hidden) + if (!window->WasActive || window->Hidden) continue; if (window->Flags & ImGuiWindowFlags_NoMouseInputs) continue; + IM_ASSERT(window->Viewport); + if (window->Viewport != g.MouseViewport) + continue; // Using the clipped AABB, a child window will typically be clipped by its parent (not always) ImVec2 hit_padding = (window->Flags & (ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize)) ? padding_regular : padding_for_resize; @@ -5331,7 +6059,7 @@ void ImGui::FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_vi if (hovered_window == NULL) hovered_window = window; IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer. - if (hovered_window_under_moving_window == NULL && (!g.MovingWindow || window->RootWindow != g.MovingWindow->RootWindow)) + if (hovered_window_under_moving_window == NULL && (!g.MovingWindow || window->RootWindowDockTree != g.MovingWindow->RootWindowDockTree)) hovered_window_under_moving_window = window; if (hovered_window && hovered_window_under_moving_window) break; @@ -5341,6 +6069,8 @@ void ImGui::FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_vi *out_hovered_window = hovered_window; if (out_hovered_window_under_moving_window != NULL) *out_hovered_window_under_moving_window = hovered_window_under_moving_window; + if (find_first_and_in_any_viewport == false && g.MovingWindow) + g.MovingWindow->Viewport = backup_moving_window_viewport; } bool ImGui::IsItemActive() @@ -5374,14 +6104,21 @@ bool ImGui::IsItemDeactivatedAfterEdit() return IsItemDeactivated() && (g.ActiveIdPreviousFrameHasBeenEditedBefore || (g.ActiveId == 0 && g.ActiveIdHasBeenEditedBefore)); } -// == GetItemID() == GetFocusID() +// == (GetItemID() == GetFocusID() && GetFocusID() != 0) bool ImGui::IsItemFocused() { ImGuiContext& g = *GImGui; if (g.NavId != g.LastItemData.ID || g.NavId == 0) return false; - return true; -} + + // Special handling for the dummy item after Begin() which represent the title bar or tab. + // When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the case. + ImGuiWindow* window = g.CurrentWindow; + if (g.LastItemData.ID == window->ID && window->WriteAccessed) + return false; + + return true; +} // Important: this can be useful but it is NOT equivalent to the behavior of e.g.Button()! // Most widgets have specific reactions based on mouse-up/down state, mouse position etc. @@ -5396,9 +6133,15 @@ bool ImGui::IsItemToggledOpen() return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledOpen) ? true : false; } +// Call after a Selectable() or TreeNode() involved in multi-selection. +// Useful if you need the per-item information before reaching EndMultiSelect(), e.g. for rendering purpose. +// This is only meant to be called inside a BeginMultiSelect()/EndMultiSelect() block. +// (Outside of multi-select, it would be misleading/ambiguous to report this signal, as widgets +// return e.g. a pressed event and user code is in charge of altering selection in ways we cannot predict.) bool ImGui::IsItemToggledSelection() { ImGuiContext& g = *GImGui; + IM_ASSERT(g.CurrentMultiSelect != NULL); // Can only be used inside a BeginMultiSelect()/EndMultiSelect() return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledSelection) ? true : false; } @@ -5420,7 +6163,7 @@ bool ImGui::IsAnyItemActive() bool ImGui::IsAnyItemFocused() { ImGuiContext& g = *GImGui; - return g.NavId != 0 && !g.NavDisableHighlight; + return g.NavId != 0 && g.NavCursorVisible; } bool ImGui::IsItemVisible() @@ -5458,7 +6201,8 @@ void ImGui::SetItemAllowOverlap() } #endif -// FIXME: It might be undesirable that this will likely disable KeyOwner-aware shortcuts systems. Consider a more fine-tuned version for the two users of this function. +// This is a shortcut for not taking ownership of 100+ keys, frequently used by drag operations. +// FIXME: It might be undesirable that this will likely disable KeyOwner-aware shortcuts systems. Consider a more fine-tuned version if needed? void ImGui::SetActiveIdUsingAllKeyboardKeys() { ImGuiContext& g = *GImGui; @@ -5493,7 +6237,7 @@ ImVec2 ImGui::GetItemRectSize() } // Prior to v1.90 2023/10/16, the BeginChild() function took a 'bool border = false' parameter instead of 'ImGuiChildFlags child_flags = 0'. -// ImGuiChildFlags_Border is defined as always == 1 in order to allow old code passing 'true'. Read comments in imgui.h for details! +// ImGuiChildFlags_Borders is defined as always == 1 in order to allow old code passing 'true'. Read comments in imgui.h for details! bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags) { ImGuiID id = GetCurrentWindow()->GetID(str_id); @@ -5512,7 +6256,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I IM_ASSERT(id != 0); // Sanity check as it is likely that some user will accidentally pass ImGuiWindowFlags into the ImGuiChildFlags argument. - const ImGuiChildFlags ImGuiChildFlags_SupportedMask_ = ImGuiChildFlags_Border | ImGuiChildFlags_AlwaysUseWindowPadding | ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle; + const ImGuiChildFlags ImGuiChildFlags_SupportedMask_ = ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding | ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle | ImGuiChildFlags_NavFlattened; IM_UNUSED(ImGuiChildFlags_SupportedMask_); IM_ASSERT((child_flags & ~ImGuiChildFlags_SupportedMask_) == 0 && "Illegal ImGuiChildFlags value. Did you pass ImGuiWindowFlags values instead of ImGuiChildFlags?"); IM_ASSERT((window_flags & ImGuiWindowFlags_AlwaysAutoResize) == 0 && "Cannot specify ImGuiWindowFlags_AlwaysAutoResize for BeginChild(). Use ImGuiChildFlags_AlwaysAutoResize!"); @@ -5524,6 +6268,8 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS if (window_flags & ImGuiWindowFlags_AlwaysUseWindowPadding) child_flags |= ImGuiChildFlags_AlwaysUseWindowPadding; + if (window_flags & ImGuiWindowFlags_NavFlattened) + child_flags |= ImGuiChildFlags_NavFlattened; #endif if (child_flags & ImGuiChildFlags_AutoResizeX) child_flags &= ~ImGuiChildFlags_ResizeX; @@ -5531,7 +6277,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I child_flags &= ~ImGuiChildFlags_ResizeY; // Set window flags - window_flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoTitleBar; + window_flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDocking; window_flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag if (child_flags & (ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize)) window_flags |= ImGuiWindowFlags_AlwaysAutoResize; @@ -5545,22 +6291,39 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I PushStyleVar(ImGuiStyleVar_ChildRounding, g.Style.FrameRounding); PushStyleVar(ImGuiStyleVar_ChildBorderSize, g.Style.FrameBorderSize); PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.FramePadding); - child_flags |= ImGuiChildFlags_Border | ImGuiChildFlags_AlwaysUseWindowPadding; + child_flags |= ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding; window_flags |= ImGuiWindowFlags_NoMove; } - // Forward child flags - g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasChildFlags; - g.NextWindowData.ChildFlags = child_flags; - // Forward size // Important: Begin() has special processing to switch condition to ImGuiCond_FirstUseEver for a given axis when ImGuiChildFlags_ResizeXXX is set. // (the alternative would to store conditional flags per axis, which is possible but more code) const ImVec2 size_avail = GetContentRegionAvail(); const ImVec2 size_default((child_flags & ImGuiChildFlags_AutoResizeX) ? 0.0f : size_avail.x, (child_flags & ImGuiChildFlags_AutoResizeY) ? 0.0f : size_avail.y); - const ImVec2 size = CalcItemSize(size_arg, size_default.x, size_default.y); + ImVec2 size = CalcItemSize(size_arg, size_default.x, size_default.y); + + // A SetNextWindowSize() call always has priority (#8020) + // (since the code in Begin() never supported SizeVal==0.0f aka auto-resize via SetNextWindowSize() call, we don't support it here for now) + // FIXME: We only support ImGuiCond_Always in this path. Supporting other paths would requires to obtain window pointer. + if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) != 0 && (g.NextWindowData.SizeCond & ImGuiCond_Always) != 0) + { + if (g.NextWindowData.SizeVal.x > 0.0f) + { + size.x = g.NextWindowData.SizeVal.x; + child_flags &= ~ImGuiChildFlags_ResizeX; + } + if (g.NextWindowData.SizeVal.y > 0.0f) + { + size.y = g.NextWindowData.SizeVal.y; + child_flags &= ~ImGuiChildFlags_ResizeY; + } + } SetNextWindowSize(size); + // Forward child flags + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasChildFlags; + g.NextWindowData.ChildFlags = child_flags; + // Build up name. If you need to append to a same child from multiple location in the ID stack, use BeginChild(ImGuiID id) with a stable value. // FIXME: 2023/11/14: commented out shorted version. We had an issue with multiple ### in child window path names, which the trailing hash helped workaround. // e.g. "ParentName###ParentIdentifier/ChildName###ChildIdentifier" would get hashed incorrectly by ImHashStr(), trailing _%08X somehow fixes it. @@ -5575,7 +6338,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I // Set style const float backup_border_size = g.Style.ChildBorderSize; - if ((child_flags & ImGuiChildFlags_Border) == 0) + if ((child_flags & ImGuiChildFlags_Borders) == 0) g.Style.ChildBorderSize = 0.0f; // Begin into window @@ -5602,7 +6365,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I const ImGuiID temp_id_for_activation = ImHashStr("##Child", 0, id); if (g.ActiveId == temp_id_for_activation) ClearActiveID(); - if (g.NavActivateId == id && !(window_flags & ImGuiWindowFlags_NavFlattened) && (child_window->DC.NavLayersActiveMask != 0 || child_window->DC.NavWindowHasScrollY)) + if (g.NavActivateId == id && !(child_flags & ImGuiChildFlags_NavFlattened) && (child_window->DC.NavLayersActiveMask != 0 || child_window->DC.NavWindowHasScrollY)) { FocusWindow(child_window); NavInitWindow(child_window, false); @@ -5628,14 +6391,15 @@ void ImGui::EndChild() ImGuiWindow* parent_window = g.CurrentWindow; ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + child_size); ItemSize(child_size); - if ((child_window->DC.NavLayersActiveMask != 0 || child_window->DC.NavWindowHasScrollY) && !(child_window->Flags & ImGuiWindowFlags_NavFlattened)) + const bool nav_flattened = (child_window->ChildFlags & ImGuiChildFlags_NavFlattened) != 0; + if ((child_window->DC.NavLayersActiveMask != 0 || child_window->DC.NavWindowHasScrollY) && !nav_flattened) { ItemAdd(bb, child_window->ChildId); - RenderNavHighlight(bb, child_window->ChildId); + RenderNavCursor(bb, child_window->ChildId); // When browsing a window that has no activable items (scroll only) we keep a highlight on the child (pass g.NavId to trick into always displaying) if (child_window->DC.NavLayersActiveMask == 0 && child_window == g.NavWindow) - RenderNavHighlight(ImRect(bb.Min - ImVec2(2, 2), bb.Max + ImVec2(2, 2)), g.NavId, ImGuiNavHighlightFlags_Compact); + RenderNavCursor(ImRect(bb.Min - ImVec2(2, 2), bb.Max + ImVec2(2, 2)), g.NavId, ImGuiNavRenderCursorFlags_Compact); } else { @@ -5645,7 +6409,7 @@ void ImGui::EndChild() ItemAdd(bb, child_window->ChildId, NULL, ImGuiItemFlags_NoNav); // But when flattened we directly reach items, adjust active layer mask accordingly - if (child_window->Flags & ImGuiWindowFlags_NavFlattened) + if (nav_flattened) parent_window->DC.NavLayersActiveMaskNext |= child_window->DC.NavLayersActiveMaskNext; } if (g.HoveredWindow == child_window) @@ -5660,6 +6424,7 @@ static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, b window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags); window->SetWindowSizeAllowFlags = enabled ? (window->SetWindowSizeAllowFlags | flags) : (window->SetWindowSizeAllowFlags & ~flags); window->SetWindowCollapsedAllowFlags = enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~flags); + window->SetWindowDockAllowFlags = enabled ? (window->SetWindowDockAllowFlags | flags) : (window->SetWindowDockAllowFlags & ~flags); } ImGuiWindow* ImGui::FindWindowByID(ImGuiID id) @@ -5676,10 +6441,19 @@ ImGuiWindow* ImGui::FindWindowByName(const char* name) static void ApplyWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settings) { - window->Pos = ImTrunc(ImVec2(settings->Pos.x, settings->Pos.y)); + const ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + window->ViewportPos = main_viewport->Pos; + if (settings->ViewportId) + { + window->ViewportId = settings->ViewportId; + window->ViewportPos = ImVec2(settings->ViewportPos.x, settings->ViewportPos.y); + } + window->Pos = ImTrunc(ImVec2(settings->Pos.x + window->ViewportPos.x, settings->Pos.y + window->ViewportPos.y)); if (settings->Size.x > 0 && settings->Size.y > 0) window->Size = window->SizeFull = ImTrunc(ImVec2(settings->Size.x, settings->Size.y)); window->Collapsed = settings->Collapsed; + window->DockId = settings->DockId; + window->DockOrder = settings->DockOrder; } static void UpdateWindowInFocusOrderList(ImGuiWindow* window, bool just_created, ImGuiWindowFlags new_flags) @@ -5712,7 +6486,8 @@ static void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* s const ImGuiViewport* main_viewport = ImGui::GetMainViewport(); window->Pos = main_viewport->Pos + ImVec2(60, 60); window->Size = window->SizeFull = ImVec2(0, 0); - window->SetWindowPosAllowFlags = window->SetWindowSizeAllowFlags = window->SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; + window->ViewportPos = main_viewport->Pos; + window->SetWindowPosAllowFlags = window->SetWindowSizeAllowFlags = window->SetWindowCollapsedAllowFlags = window->SetWindowDockAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; if (settings != NULL) { @@ -5760,6 +6535,16 @@ static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags) return window; } +static ImGuiWindow* GetWindowForTitleDisplay(ImGuiWindow* window) +{ + return window->DockNodeAsHost ? window->DockNodeAsHost->VisibleWindow : window; +} + +static ImGuiWindow* GetWindowForTitleAndMenuHeight(ImGuiWindow* window) +{ + return (window->DockNodeAsHost && window->DockNodeAsHost->VisibleWindow) ? window->DockNodeAsHost->VisibleWindow : window; +} + static inline ImVec2 CalcWindowMinSize(ImGuiWindow* window) { // We give windows non-zero minimum size to facilitate understanding problematic cases (e.g. empty popups) @@ -5779,7 +6564,7 @@ static inline ImVec2 CalcWindowMinSize(ImGuiWindow* window) } // Reduce artifacts with very small windows - ImGuiWindow* window_for_height = window; + ImGuiWindow* window_for_height = GetWindowForTitleAndMenuHeight(window); size_min.y = ImMax(size_min.y, window_for_height->TitleBarHeight + window_for_height->MenuBarHeight + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); return size_min; } @@ -5850,8 +6635,19 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont { // Maximum window size is determined by the viewport size or monitor size ImVec2 size_min = CalcWindowMinSize(window); - ImVec2 size_max = ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_Popup)) ? ImVec2(FLT_MAX, FLT_MAX) : ImGui::GetMainViewport()->WorkSize - style.DisplaySafeAreaPadding * 2.0f; - ImVec2 size_auto_fit = ImClamp(size_desired, size_min, size_max); + ImVec2 size_max = ImVec2(FLT_MAX, FLT_MAX); + + // Child windows are layed within their parent (unless they are also popups/menus) and thus have no restriction + if ((window->Flags & ImGuiWindowFlags_ChildWindow) == 0 || (window->Flags & ImGuiWindowFlags_Popup) != 0) + { + if (!window->ViewportOwned) + size_max = ImGui::GetMainViewport()->WorkSize - style.DisplaySafeAreaPadding * 2.0f; + const int monitor_idx = window->ViewportAllowPlatformMonitorExtend; + if (monitor_idx >= 0 && monitor_idx < g.PlatformIO.Monitors.Size) + size_max = g.PlatformIO.Monitors[monitor_idx].WorkSize - style.DisplaySafeAreaPadding * 2.0f; + } + + ImVec2 size_auto_fit = ImClamp(size_desired, size_min, ImMax(size_min, size_max)); // FIXME: CalcWindowAutoFitSize() doesn't take into account that only one axis may be auto-fit when calculating scrollbars, // we may need to compute/store three variants of size_auto_fit, for x/y/xy. @@ -5888,7 +6684,7 @@ static ImGuiCol GetWindowBgColorIdx(ImGuiWindow* window) { if (window->Flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) return ImGuiCol_PopupBg; - if (window->Flags & ImGuiWindowFlags_ChildWindow) + if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !window->DockIsActive) return ImGuiCol_ChildBg; return ImGuiCol_WindowBg; } @@ -5954,7 +6750,7 @@ static ImRect GetResizeBorderRect(ImGuiWindow* window, int border_n, float perp_ ImGuiID ImGui::GetWindowResizeCornerID(ImGuiWindow* window, int n) { IM_ASSERT(n >= 0 && n < 4); - ImGuiID id = window->ID; + ImGuiID id = window->DockIsActive ? window->DockNode->HostWindow->ID : window->ID; id = ImHashStr("#RESIZE", 0, id); id = ImHashData(&n, sizeof(int), id); return id; @@ -5965,7 +6761,7 @@ ImGuiID ImGui::GetWindowResizeBorderID(ImGuiWindow* window, ImGuiDir dir) { IM_ASSERT(dir >= 0 && dir < 4); int n = (int)dir + 4; - ImGuiID id = window->ID; + ImGuiID id = window->DockIsActive ? window->DockNode->HostWindow->ID : window->ID; id = ImHashStr("#RESIZE", 0, id); id = ImHashData(&n, sizeof(int), id); return id; @@ -5996,6 +6792,16 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si ImVec2 pos_target(FLT_MAX, FLT_MAX); ImVec2 size_target(FLT_MAX, FLT_MAX); + // Clip mouse interaction rectangles within the viewport rectangle (in practice the narrowing is going to happen most of the time). + // - Not narrowing would mostly benefit the situation where OS windows _without_ decoration have a threshold for hovering when outside their limits. + // This is however not the case with current backends under Win32, but a custom borderless window implementation would benefit from it. + // - When decoration are enabled we typically benefit from that distance, but then our resize elements would be conflicting with OS resize elements, so we also narrow. + // - Note that we are unable to tell if the platform setup allows hovering with a distance threshold (on Win32, decorated window have such threshold). + // We only clip interaction so we overwrite window->ClipRect, cannot call PushClipRect() yet as DrawList is not yet setup. + const bool clip_with_viewport_rect = !(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport) || (g.IO.MouseHoveredViewport != window->ViewportId) || !(window->Viewport->Flags & ImGuiViewportFlags_NoDecoration); + if (clip_with_viewport_rect) + window->ClipRect = window->Viewport->GetMainRect(); + // Resize grips and borders are on layer 1 window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; @@ -6016,7 +6822,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si ButtonBehavior(resize_rect, resize_grip_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus); //GetForegroundDrawList(window)->AddRect(resize_rect.Min, resize_rect.Max, IM_COL32(255, 255, 0, 255)); if (hovered || held) - g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; + SetMouseCursor((resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE); if (held && g.IO.MouseDoubleClicked[0]) { @@ -6062,7 +6868,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si if (hovered && g.HoveredIdTimer <= WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER) hovered = false; if (hovered || held) - g.MouseCursor = (axis == ImGuiAxis_X) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS; + SetMouseCursor((axis == ImGuiAxis_X) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS); if (held && g.IO.MouseDoubleClicked[0]) { // Double-clicking bottom or right border auto-fit on this axis @@ -6081,7 +6887,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si // Switch to relative resizing mode when border geometry moved (e.g. resizing a child altering parent scroll), in order to avoid resizing feedback loop. // Currently only using relative mode on resizable child windows, as the problem to solve is more likely noticeable for them, but could apply for all windows eventually. // FIXME: May want to generalize this idiom at lower-level, so more widgets can use it! - const bool just_scrolled_manually_while_resizing = (g.WheelingWindow != NULL && g.WheelingWindowScrolledFrame == g.FrameCount && IsWindowChildOf(window, g.WheelingWindow, false)); + const bool just_scrolled_manually_while_resizing = (g.WheelingWindow != NULL && g.WheelingWindowScrolledFrame == g.FrameCount && IsWindowChildOf(window, g.WheelingWindow, false, true)); if (g.ActiveIdIsJustActivated || just_scrolled_manually_while_resizing) { g.WindowResizeBorderExpectedRect = border_rect; @@ -6114,12 +6920,13 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si border_target = ImClamp(border_target, clamp_min, clamp_max); if (flags & ImGuiWindowFlags_ChildWindow) // Clamp resizing of childs within parent { - ImGuiWindowFlags parent_flags = window->ParentWindow->Flags; - ImRect border_limit_rect = window->ParentWindow->InnerRect; - border_limit_rect.Expand(ImVec2(-ImMax(window->WindowPadding.x, window->WindowBorderSize), -ImMax(window->WindowPadding.y, window->WindowBorderSize))); - if ((parent_flags & (ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar)) == 0 || (parent_flags & ImGuiWindowFlags_NoScrollbar)) + ImGuiWindow* parent_window = window->ParentWindow; + ImGuiWindowFlags parent_flags = parent_window->Flags; + ImRect border_limit_rect = parent_window->InnerRect; + border_limit_rect.Expand(ImVec2(-ImMax(parent_window->WindowPadding.x, parent_window->WindowBorderSize), -ImMax(parent_window->WindowPadding.y, parent_window->WindowBorderSize))); + if ((axis == ImGuiAxis_X) && ((parent_flags & (ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar)) == 0 || (parent_flags & ImGuiWindowFlags_NoScrollbar))) border_target.x = ImClamp(border_target.x, border_limit_rect.Min.x, border_limit_rect.Max.x); - if (parent_flags & ImGuiWindowFlags_NoScrollbar) + if ((axis == ImGuiAxis_Y) && (parent_flags & ImGuiWindowFlags_NoScrollbar)) border_target.y = ImClamp(border_target.y, border_limit_rect.Min.y, border_limit_rect.Max.y); } if (!ignore_resize) @@ -6138,7 +6945,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si // Navigation resize (keyboard/gamepad) // FIXME: This cannot be moved to NavUpdateWindowing() because CalcWindowSizeAfterConstraint() need to callback into user. // Not even sure the callback works here. - if (g.NavWindowingTarget && g.NavWindowingTarget->RootWindow == window) + if (g.NavWindowingTarget && g.NavWindowingTarget->RootWindowDockTree == window) { ImVec2 nav_resize_dir; if (g.NavInputSource == ImGuiInputSource_Keyboard && g.IO.KeyShift) @@ -6152,7 +6959,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si g.NavWindowingAccumDeltaSize += nav_resize_dir * resize_step; g.NavWindowingAccumDeltaSize = ImMax(g.NavWindowingAccumDeltaSize, clamp_rect.Min - window->Pos - window->Size); // We need Pos+Size >= clmap_rect.Min, so Size >= clmap_rect.Min - Pos, so size_delta >= clmap_rect.Min - window->Pos - window->Size g.NavWindowingToggleLayer = false; - g.NavDisableMouseHover = true; + g.NavHighlightItemUnderNav = true; resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive); ImVec2 accum_floored = ImTrunc(g.NavWindowingAccumDeltaSize); if (accum_floored.x != 0.0f || accum_floored.y != 0.0f) @@ -6189,7 +6996,9 @@ static inline void ClampWindowPos(ImGuiWindow* window, const ImRect& visibility_ { ImGuiContext& g = *GImGui; ImVec2 size_for_clamping = window->Size; - if (g.IO.ConfigWindowsMoveFromTitleBarOnly && !(window->Flags & ImGuiWindowFlags_NoTitleBar)) + if (g.IO.ConfigWindowsMoveFromTitleBarOnly && window->DockNodeAsHost) + size_for_clamping.y = ImGui::GetFrameHeight(); // Not using window->TitleBarHeight() as DockNodeAsHost will report 0.0f here. + else if (g.IO.ConfigWindowsMoveFromTitleBarOnly && !(window->Flags & ImGuiWindowFlags_NoTitleBar)) size_for_clamping.y = window->TitleBarHeight; window->Pos = ImClamp(window->Pos, visibility_rect.Min - size_for_clamping, visibility_rect.Max); } @@ -6224,7 +7033,7 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) const ImU32 border_col_resizing = GetColorU32((window->ResizeBorderHeld != -1) ? ImGuiCol_SeparatorActive : ImGuiCol_SeparatorHovered); RenderWindowOuterSingleBorder(window, border_n, border_col_resizing, ImMax(2.0f, window->WindowBorderSize)); // Thicker than usual } - if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar)) + if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive) { float y = window->Pos.y + window->TitleBarHeight - 1; window->DrawList->AddLine(ImVec2(window->Pos.x + border_size, y), ImVec2(window->Pos.x + window->Size.x - border_size, y), border_col, g.Style.FrameBorderSize); @@ -6252,7 +7061,9 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar // Title bar only const float backup_border_size = style.FrameBorderSize; g.Style.FrameBorderSize = window->WindowBorderSize; - ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && !g.NavDisableHighlight) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed); + ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && g.NavCursorVisible) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed); + if (window->ViewportOwned) + title_bar_col |= IM_COL32_A_MASK; // No alpha (we don't support is_docking_transparent_payload here because simpler and less meaningful, but could with a bit of code shuffle/reuse) RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding); g.Style.FrameBorderSize = backup_border_size; } @@ -6261,23 +7072,58 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar // Window background if (!(flags & ImGuiWindowFlags_NoBackground)) { + bool is_docking_transparent_payload = false; + if (g.DragDropActive && (g.FrameCount - g.DragDropAcceptFrameCount) <= 1 && g.IO.ConfigDockingTransparentPayload) + if (g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) && *(ImGuiWindow**)g.DragDropPayload.Data == window) + is_docking_transparent_payload = true; + ImU32 bg_col = GetColorU32(GetWindowBgColorIdx(window)); - bool override_alpha = false; - float alpha = 1.0f; - if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasBgAlpha) + if (window->ViewportOwned) + { + bg_col |= IM_COL32_A_MASK; // No alpha + if (is_docking_transparent_payload) + window->Viewport->Alpha *= DOCKING_TRANSPARENT_PAYLOAD_ALPHA; + } + else { - alpha = g.NextWindowData.BgAlphaVal; - override_alpha = true; + // Adjust alpha. For docking + bool override_alpha = false; + float alpha = 1.0f; + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasBgAlpha) + { + alpha = g.NextWindowData.BgAlphaVal; + override_alpha = true; + } + if (is_docking_transparent_payload) + { + alpha *= DOCKING_TRANSPARENT_PAYLOAD_ALPHA; // FIXME-DOCK: Should that be an override? + override_alpha = true; + } + if (override_alpha) + bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(alpha) << IM_COL32_A_SHIFT); } - if (override_alpha) - bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(alpha) << IM_COL32_A_SHIFT); - window->DrawList->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? 0 : ImDrawFlags_RoundCornersBottom); + + // Render, for docked windows and host windows we ensure bg goes before decorations + if (window->DockIsActive) + window->DockNode->LastBgColor = bg_col; + ImDrawList* bg_draw_list = window->DockIsActive ? window->DockNode->HostWindow->DrawList : window->DrawList; + if (window->DockIsActive || (flags & ImGuiWindowFlags_DockNodeHost)) + bg_draw_list->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG); + bg_draw_list->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? 0 : ImDrawFlags_RoundCornersBottom); + if (window->DockIsActive || (flags & ImGuiWindowFlags_DockNodeHost)) + bg_draw_list->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_FG); } + if (window->DockIsActive) + window->DockNode->IsBgDrawnThisFrame = true; // Title bar - if (!(flags & ImGuiWindowFlags_NoTitleBar)) + // (when docked, DockNode are drawing their own title bar. Individual windows however do NOT set the _NoTitleBar flag, + // in order for their pos/size to be matching their undocking state.) + if (!(flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive) { ImU32 title_bar_col = GetColorU32(title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg); + if (window->ViewportOwned) + title_bar_col |= IM_COL32_A_MASK; // No alpha window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawFlags_RoundCornersTop); } @@ -6291,6 +7137,27 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); } + // Docking: Unhide tab bar (small triangle in the corner), drag from small triangle to quickly undock + ImGuiDockNode* node = window->DockNode; + if (window->DockIsActive && node->IsHiddenTabBar() && !node->IsNoTabBar()) + { + float unhide_sz_draw = ImTrunc(g.FontSize * 0.70f); + float unhide_sz_hit = ImTrunc(g.FontSize * 0.55f); + ImVec2 p = node->Pos; + ImRect r(p, p + ImVec2(unhide_sz_hit, unhide_sz_hit)); + ImGuiID unhide_id = window->GetID("#UNHIDE"); + KeepAliveID(unhide_id); + bool hovered, held; + if (ButtonBehavior(r, unhide_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren)) + node->WantHiddenTabBarToggle = true; + else if (held && IsMouseDragging(0)) + StartMouseMovingWindowOrNode(window, node, true); // Undock from tab-bar triangle = same as window/collapse menu button + + // FIXME-DOCK: Ideally we'd use ImGuiCol_TitleBgActive/ImGuiCol_TitleBg here, but neither is guaranteed to be visible enough at this sort of size.. + ImU32 col = GetColorU32(((held && hovered) || (node->IsFocused && !hovered)) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + window->DrawList->AddTriangleFilled(p, p + ImVec2(unhide_sz_draw, 0.0f), p + ImVec2(0.0f, unhide_sz_draw), col); + } + // Scrollbars if (window->ScrollbarX) Scrollbar(ImGuiAxis_X); @@ -6314,12 +7181,13 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar } } - // Borders - if (handle_borders_and_resize_grips) + // Borders (for dock node host they will be rendered over after the tab bar) + if (handle_borders_and_resize_grips && !window->DockNodeAsHost) RenderWindowOuterBorders(window); } } +// When inside a dock node, this is handled in DockNodeCalcTabBarLayout() instead. // Render title text, collapse button, close button void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open) { @@ -6361,7 +7229,7 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl // Collapse button (submitting first so it gets priority when choosing a navigation init fallback) if (has_collapse_button) - if (CollapseButton(window->GetID("#COLLAPSE"), collapse_button_pos)) + if (CollapseButton(window->GetID("#COLLAPSE"), collapse_button_pos, NULL)) window->WantCollapseToggle = true; // Defer actual collapsing to next frame as we are too far in the Begin() function // Close button @@ -6412,14 +7280,18 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window) { window->ParentWindow = parent_window; - window->RootWindow = window->RootWindowPopupTree = window->RootWindowForTitleBarHighlight = window->RootWindowForNav = window; + window->RootWindow = window->RootWindowPopupTree = window->RootWindowDockTree = window->RootWindowForTitleBarHighlight = window->RootWindowForNav = window; if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip)) - window->RootWindow = parent_window->RootWindow; + { + window->RootWindowDockTree = parent_window->RootWindowDockTree; + if (!window->DockIsActive && !(parent_window->Flags & ImGuiWindowFlags_DockNodeHost)) + window->RootWindow = parent_window->RootWindow; + } if (parent_window && (flags & ImGuiWindowFlags_Popup)) window->RootWindowPopupTree = parent_window->RootWindowPopupTree; - if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) + if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) // FIXME: simply use _NoTitleBar ? window->RootWindowForTitleBarHighlight = parent_window->RootWindowForTitleBarHighlight; - while (window->RootWindowForNav->Flags & ImGuiWindowFlags_NavFlattened) + while (window->RootWindowForNav->ChildFlags & ImGuiChildFlags_NavFlattened) { IM_ASSERT(window->RootWindowForNav->ParentWindow != NULL); window->RootWindowForNav = window->RootWindowForNav->ParentWindow; @@ -6441,15 +7313,28 @@ void ImGui::UpdateWindowSkipRefresh(ImGuiWindow* window) return; if (window->Hidden) // If was hidden (previous frame) return; - if ((g.NextWindowData.RefreshFlagsVal & ImGuiWindowRefreshFlags_RefreshOnHover) && g.HoveredWindow && window->RootWindow == g.HoveredWindow->RootWindow) - return; - if ((g.NextWindowData.RefreshFlagsVal & ImGuiWindowRefreshFlags_RefreshOnFocus) && g.NavWindow && window->RootWindow == g.NavWindow->RootWindow) - return; + if ((g.NextWindowData.RefreshFlagsVal & ImGuiWindowRefreshFlags_RefreshOnHover) && g.HoveredWindow) + if (window->RootWindow == g.HoveredWindow->RootWindow || IsWindowWithinBeginStackOf(g.HoveredWindow->RootWindow, window)) + return; + if ((g.NextWindowData.RefreshFlagsVal & ImGuiWindowRefreshFlags_RefreshOnFocus) && g.NavWindow) + if (window->RootWindow == g.NavWindow->RootWindow || IsWindowWithinBeginStackOf(g.NavWindow->RootWindow, window)) + return; window->DrawList = NULL; window->SkipRefresh = true; } } +static void SetWindowActiveForSkipRefresh(ImGuiWindow* window) +{ + window->Active = true; + for (ImGuiWindow* child : window->DC.ChildWindows) + if (!child->Hidden) + { + child->Active = child->SkipRefresh = true; + SetWindowActiveForSkipRefresh(child); + } +} + // When a modal popup is open, newly created windows that want focus (i.e. are not popups and do not specify ImGuiWindowFlags_NoFocusOnAppearing) // should be positioned behind that modal window, unless the window was created inside the modal begin-stack. // In case of multiple stacked modals newly created window honors begin stack order and does not go below its own modal parent. @@ -6515,29 +7400,28 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if ((flags & ImGuiWindowFlags_NoInputs) == ImGuiWindowFlags_NoInputs) flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; - if (flags & ImGuiWindowFlags_NavFlattened) - IM_ASSERT(flags & ImGuiWindowFlags_ChildWindow); - const int current_frame = g.FrameCount; const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame); window->IsFallbackWindow = (g.CurrentWindowStack.Size == 0 && g.WithinFrameScopeWithImplicitWindow); - // Update the Appearing flag - bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on + // Update the Appearing flag (note: the BeginDocked() path may also set this to true later) + bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on if (flags & ImGuiWindowFlags_Popup) { ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size]; window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed window_just_activated_by_user |= (window != popup_ref.Window); } - window->Appearing = window_just_activated_by_user; - if (window->Appearing) - SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); // Update Flags, LastFrameActive, BeginOrderXXX fields + const bool window_was_appearing = window->Appearing; if (first_begin_of_the_frame) { UpdateWindowInFocusOrderList(window, window_just_created, flags); + window->Appearing = window_just_activated_by_user; + if (window->Appearing) + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); + window->FlagsPreviousFrame = window->Flags; window->Flags = (ImGuiWindowFlags)flags; window->ChildFlags = (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasChildFlags) ? g.NextWindowData.ChildFlags : 0; window->LastFrameActive = current_frame; @@ -6550,8 +7434,42 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) flags = window->Flags; } + // Docking + // (NB: during the frame dock nodes are created, it is possible that (window->DockIsActive == false) even though (window->DockNode->Windows.Size > 1) + IM_ASSERT(window->DockNode == NULL || window->DockNodeAsHost == NULL); // Cannot be both + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasDock) + SetWindowDock(window, g.NextWindowData.DockId, g.NextWindowData.DockCond); + if (first_begin_of_the_frame) + { + bool has_dock_node = (window->DockId != 0 || window->DockNode != NULL); + bool new_auto_dock_node = !has_dock_node && GetWindowAlwaysWantOwnTabBar(window); + bool dock_node_was_visible = window->DockNodeIsVisible; + bool dock_tab_was_visible = window->DockTabIsVisible; + if (has_dock_node || new_auto_dock_node) + { + BeginDocked(window, p_open); + flags = window->Flags; + if (window->DockIsActive) + { + IM_ASSERT(window->DockNode != NULL); + g.NextWindowData.Flags &= ~ImGuiNextWindowDataFlags_HasSizeConstraint; // Docking currently override constraints + } + + // Amend the Appearing flag + if (window->DockTabIsVisible && !dock_tab_was_visible && dock_node_was_visible && !window->Appearing && !window_was_appearing) + { + window->Appearing = true; + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); + } + } + else + { + window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false; + } + } + // Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from a different window stack - ImGuiWindow* parent_window_in_stack = g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back().Window; + ImGuiWindow* parent_window_in_stack = (window->DockIsActive && window->DockNode->HostWindow) ? window->DockNode->HostWindow : g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back().Window; ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow; IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow)); @@ -6561,12 +7479,13 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Add to stack g.CurrentWindow = window; - ImGuiWindowStackData window_stack_data; + g.CurrentWindowStack.resize(g.CurrentWindowStack.Size + 1); + ImGuiWindowStackData& window_stack_data = g.CurrentWindowStack.back(); window_stack_data.Window = window; window_stack_data.ParentLastItemDataBackup = g.LastItemData; - window_stack_data.StackSizesOnBegin.SetToContextState(&g); - window_stack_data.DisabledOverrideReenable = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; - g.CurrentWindowStack.push_back(window_stack_data); + window_stack_data.DisabledOverrideReenable = (flags & ImGuiWindowFlags_Tooltip) && (g.CurrentItemFlags & ImGuiItemFlags_Disabled); + ErrorRecoveryStoreState(&window_stack_data.StackSizesInBegin); + g.StackSizesInBeginForCurrentWindow = &window_stack_data.StackSizesInBegin; if (flags & ImGuiWindowFlags_ChildMenu) g.BeginMenuDepth++; @@ -6576,13 +7495,24 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) UpdateWindowParentAndRootLinks(window, flags, parent_window); window->ParentWindowInBeginStack = parent_window_in_stack; + // Focus route // There's little point to expose a flag to set this: because the interesting cases won't be using parent_window_in_stack, - // e.g. linking a tool window in a standalone viewport to a document window, regardless of their Begin() stack parenting. (#6798) - window->ParentWindowForFocusRoute = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window_in_stack : NULL; + // Use for e.g. linking a tool window in a standalone viewport to a document window, regardless of their Begin() stack parenting. (#6798) + window->ParentWindowForFocusRoute = (window->RootWindow != window) ? parent_window_in_stack : NULL; + if (window->ParentWindowForFocusRoute == NULL && window->DockNode != NULL) + if (window->DockNode->MergedFlags & ImGuiDockNodeFlags_DockedWindowsInFocusRoute) + window->ParentWindowForFocusRoute = window->DockNode->HostWindow; + + // Override with SetNextWindowClass() field or direct call to SetWindowParentWindowForFocusRoute() + if (window->WindowClass.FocusRouteParentWindowId != 0) + { + window->ParentWindowForFocusRoute = FindWindowByID(window->WindowClass.FocusRouteParentWindowId); + IM_ASSERT(window->ParentWindowForFocusRoute != 0); // Invalid value for FocusRouteParentWindowId. + } } // Add to focus scope stack - PushFocusScope((flags & ImGuiWindowFlags_NavFlattened) ? g.CurrentFocusScopeId : window->ID); + PushFocusScope((window->ChildFlags & ImGuiChildFlags_NavFlattened) ? g.CurrentFocusScopeId : window->ID); window->NavRootFocusScopeId = g.CurrentFocusScopeId; // Add to popup stacks: update OpenPopupStack[] data, push to BeginPopupStack[] @@ -6642,6 +7572,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->ContentSizeExplicit = g.NextWindowData.ContentSizeVal; else if (first_begin_of_the_frame) window->ContentSizeExplicit = ImVec2(0.0f, 0.0f); + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasWindowClass) + window->WindowClass = g.NextWindowData.WindowClass; if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasCollapsed) SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond); if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasFocus) @@ -6671,6 +7603,11 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->IDStack.resize(1); window->DrawList->_ResetForNewFrame(); window->DC.CurrentTableIdx = -1; + if (flags & ImGuiWindowFlags_DockNodeHost) + { + window->DrawList->ChannelsSplit(2); + window->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_FG); // Render decorations on channel 1 as we will render the backgrounds manually later + } // Restore buffer capacity when woken from a compacted state, to avoid if (window->MemoryCompacted) @@ -6679,7 +7616,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Update stored window name when it changes (which can _only_ happen with the "###" operator, so the ID would stay unchanged). // The title bar always display the 'name' parameter, so we only update the string storage if it needs to be visible to the end-user elsewhere. bool window_title_visible_elsewhere = false; - if (g.NavWindowingListWindow != NULL && (window->Flags & ImGuiWindowFlags_NoNavFocus) == 0) // Window titles visible when using CTRL+TAB + if ((window->Viewport && window->Viewport->Window == window) || (window->DockIsActive)) + window_title_visible_elsewhere = true; + else if (g.NavWindowingListWindow != NULL && (window->Flags & ImGuiWindowFlags_NoNavFocus) == 0) // Window titles visible when using CTRL+TAB window_title_visible_elsewhere = true; if (window_title_visible_elsewhere && !window_just_created && strcmp(name, window->Name) != 0) { @@ -6692,6 +7631,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Update contents size from last frame for auto-fitting (or use explicit size) CalcWindowContentSizes(window, &window->ContentSize, &window->ContentSizeIdeal); + + // FIXME: These flags are decremented before they are used. This means that in order to have these fields produce their intended behaviors + // for one frame we must set them to at least 2, which is counter-intuitive. HiddenFramesCannotSkipItems is a more complicated case because + // it has a single usage before this code block and may be set below before it is finally checked. if (window->HiddenFramesCanSkipItems > 0) window->HiddenFramesCanSkipItems--; if (window->HiddenFramesCannotSkipItems > 0) @@ -6719,20 +7662,23 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } // SELECT VIEWPORT - // FIXME-VIEWPORT: In the docking/viewport branch, this is the point where we select the current viewport (which may affect the style) + // We need to do this before using any style/font sizes, as viewport with a different DPI may affect font sizes. - ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport(); - SetWindowViewport(window, viewport); + WindowSelectViewport(window); + SetCurrentViewport(window, window->Viewport); + window->FontDpiScale = (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ? window->Viewport->DpiScale : 1.0f; SetCurrentWindow(window); + flags = window->Flags; // LOCK BORDER SIZE AND PADDING FOR THE FRAME (so that altering them doesn't cause inconsistencies) + // We read Style data after the call to UpdateSelectWindowViewport() which might be swapping the style. - if (flags & ImGuiWindowFlags_ChildWindow) + if (!window->DockIsActive && (flags & ImGuiWindowFlags_ChildWindow)) window->WindowBorderSize = style.ChildBorderSize; else window->WindowBorderSize = ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize; window->WindowPadding = style.WindowPadding; - if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !(window->ChildFlags & ImGuiChildFlags_AlwaysUseWindowPadding) && window->WindowBorderSize == 0.0f) + if (!window->DockIsActive && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !(window->ChildFlags & ImGuiChildFlags_AlwaysUseWindowPadding) && window->WindowBorderSize == 0.0f) window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f); // Lock menu offset so size calculation can use it as menu-bar windows need a minimum size. @@ -6752,11 +7698,12 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Collapse window by double-clicking on title bar // At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing - if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse)) + if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse) && !window->DockIsActive) { - // We don't use a regular button+id to test for double-click on title bar (mostly due to legacy reason, could be fixed), so verify that we don't have items over the title bar. + // We don't use a regular button+id to test for double-click on title bar (mostly due to legacy reason, could be fixed), + // so verify that we don't have items over the title bar. ImRect title_bar_rect = window->TitleBarRect(); - if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max)) + if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && g.ActiveId == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max)) if (g.IO.MouseClickedCount[0] == 2 && GetKeyOwner(ImGuiKey_MouseLeft) == ImGuiKeyOwner_NoOwner) window->WantCollapseToggle = true; if (window->WantCollapseToggle) @@ -6852,23 +7799,66 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip) window->Pos = FindBestWindowPosForPopup(window); + // Late create viewport if we don't fit within our current host viewport. + if (window->ViewportAllowPlatformMonitorExtend >= 0 && !window->ViewportOwned && !(window->Viewport->Flags & ImGuiViewportFlags_IsMinimized)) + if (!window->Viewport->GetMainRect().Contains(window->Rect())) + { + // This is based on the assumption that the DPI will be known ahead (same as the DPI of the selection done in UpdateSelectWindowViewport) + //ImGuiViewport* old_viewport = window->Viewport; + window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_NoFocusOnAppearing); + + // FIXME-DPI + //IM_ASSERT(old_viewport->DpiScale == window->Viewport->DpiScale); // FIXME-DPI: Something went wrong + SetCurrentViewport(window, window->Viewport); + window->FontDpiScale = (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ? window->Viewport->DpiScale : 1.0f; + SetCurrentWindow(window); + } + + if (window->ViewportOwned) + WindowSyncOwnedViewport(window, parent_window_in_stack); + // Calculate the range of allowed position for that window (to be movable and visible past safe area padding) // When clamping to stay visible, we will enforce that window->Pos stays inside of visibility_rect. - ImRect viewport_rect(viewport->GetMainRect()); - ImRect viewport_work_rect(viewport->GetWorkRect()); + ImRect viewport_rect(window->Viewport->GetMainRect()); + ImRect viewport_work_rect(window->Viewport->GetWorkRect()); ImVec2 visibility_padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding); ImRect visibility_rect(viewport_work_rect.Min + visibility_padding, viewport_work_rect.Max - visibility_padding); // Clamp position/size so window stays visible within its viewport or monitor // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing. + // FIXME: Similar to code in GetWindowAllowedExtentRect() if (!window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow)) - if (viewport_rect.GetWidth() > 0.0f && viewport_rect.GetHeight() > 0.0f) + { + if (!window->ViewportOwned && viewport_rect.GetWidth() > 0 && viewport_rect.GetHeight() > 0.0f) + { + ClampWindowPos(window, visibility_rect); + } + else if (window->ViewportOwned && g.PlatformIO.Monitors.Size > 0) + { + if (g.MovingWindow != NULL && window->RootWindowDockTree == g.MovingWindow->RootWindowDockTree) + { + // While moving windows we allow them to straddle monitors (#7299, #3071) + visibility_rect = g.PlatformMonitorsFullWorkRect; + } + else + { + // When not moving ensure visible in its monitor + // Lost windows (e.g. a monitor disconnected) will naturally moved to the fallback/dummy monitor aka the main viewport. + const ImGuiPlatformMonitor* monitor = GetViewportPlatformMonitor(window->Viewport); + visibility_rect = ImRect(monitor->WorkPos, monitor->WorkPos + monitor->WorkSize); + } + visibility_rect.Expand(-visibility_padding); ClampWindowPos(window, visibility_rect); + } + } window->Pos = ImTrunc(window->Pos); // Lock window rounding for the frame (so that altering them doesn't cause inconsistencies) // Large values tend to lead to variety of artifacts and are not recommended. - window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding; + if (window->ViewportOwned || window->DockIsActive) + window->WindowRounding = 0.0f; + else + window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding; // For windows with title bar or menu bar, we clamp to FrameHeight(FontSize + FramePadding.y * 2.0f) to completely hide artifacts. //if ((window->Flags & ImGuiWindowFlags_MenuBar) || !(window->Flags & ImGuiWindowFlags_NoTitleBar)) @@ -6880,7 +7870,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { if (flags & ImGuiWindowFlags_Popup) want_focus = true; - else if ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) == 0) + else if ((window->DockIsActive || (flags & ImGuiWindowFlags_ChildWindow) == 0) && !(flags & ImGuiWindowFlags_Tooltip)) want_focus = true; } @@ -6896,12 +7886,15 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } #endif + // Decide if we are going to handle borders and resize grips + const bool handle_borders_and_resize_grips = (window->DockNodeAsHost || !window->DockIsActive); + // Handle manual resize: Resize Grips, Borders, Gamepad int border_hovered = -1, border_held = -1; ImU32 resize_grip_col[4] = {}; const int resize_grip_count = ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup)) ? 0 : g.IO.ConfigWindowsResizeFromEdges ? 2 : 1; // Allow resize from lower-left if we have the mouse cursor feedback for it. const float resize_grip_draw_size = IM_TRUNC(ImMax(g.FontSize * 1.10f, window->WindowRounding + 1.0f + g.FontSize * 0.2f)); - if (!window->Collapsed) + if (handle_borders_and_resize_grips && !window->Collapsed) if (int auto_fit_mask = UpdateWindowManualResize(window, size_auto_fit, &border_hovered, &border_held, resize_grip_count, &resize_grip_col[0], visibility_rect)) { if (auto_fit_mask & (1 << ImGuiAxis_X)) @@ -6912,6 +7905,20 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->ResizeBorderHovered = (signed char)border_hovered; window->ResizeBorderHeld = (signed char)border_held; + // Synchronize window --> viewport again and one last time (clamping and manual resize may have affected either) + if (window->ViewportOwned) + { + if (!window->Viewport->PlatformRequestMove) + window->Viewport->Pos = window->Pos; + if (!window->Viewport->PlatformRequestResize) + window->Viewport->Size = window->Size; + window->Viewport->UpdateWorkRect(); + viewport_rect = window->Viewport->GetMainRect(); + } + + // Save last known viewport position within the window itself (so it can be saved in .ini file and restored) + window->ViewportPos = window->Viewport->Pos; + // SCROLLBAR VISIBILITY // Update scrollbar visibility (based on the Size that was effective during last frame or the auto-resized Size). @@ -6950,6 +7957,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) const ImRect outer_rect = window->Rect(); const ImRect title_bar_rect = window->TitleBarRect(); window->OuterRectClipped = outer_rect; + if (window->DockIsActive) + window->OuterRectClipped.Min.y += window->TitleBarHeight; window->OuterRectClipped.ClipWith(host_rect); // Inner rectangle @@ -6973,10 +7982,14 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Affected by window/frame border size. Used by: // - Begin() initial clip rect float top_border_size = (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize); - window->InnerClipRect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + window->WindowBorderSize); - window->InnerClipRect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y + top_border_size); - window->InnerClipRect.Max.x = ImFloor(0.5f + window->InnerRect.Max.x - window->WindowBorderSize); - window->InnerClipRect.Max.y = ImFloor(0.5f + window->InnerRect.Max.y - window->WindowBorderSize); + + // Try to match the fact that our border is drawn centered over the window rectangle, rather than inner. + // This is why we do a *0.5f here. We don't currently even technically support large values for WindowBorderSize, + // see e.g #7887 #7888, but may do after we move the window border to become an inner border (and then we can remove the 0.5f here). + window->InnerClipRect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + window->WindowBorderSize * 0.5f); + window->InnerClipRect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y + top_border_size * 0.5f); + window->InnerClipRect.Max.x = ImFloor(window->InnerRect.Max.x - window->WindowBorderSize * 0.5f); + window->InnerClipRect.Max.y = ImFloor(window->InnerRect.Max.y - window->WindowBorderSize * 0.5f); window->InnerClipRect.ClipWithFull(host_rect); // Default item width. Make it proportional to window size if window manually resizes @@ -7008,6 +8021,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Child windows can render their decoration (bg color, border, scrollbars, etc.) within their parent to save a draw call (since 1.71) // When using overlapping child windows, this will break the assumption that child z-order is mapped to submission order. // FIXME: User code may rely on explicit sorting of overlapping child window and would need to disable this somehow. Please get in contact if you are affected (github #4493) + const bool is_undocked_or_docked_visible = !window->DockIsActive || window->DockTabIsVisible; + if (is_undocked_or_docked_visible) { bool render_decorations_in_parent = false; if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip) @@ -7025,8 +8040,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Handle title bar, scrollbar, resize grips and resize borders const ImGuiWindow* window_to_highlight = g.NavWindowingTarget ? g.NavWindowingTarget : g.NavWindow; - const bool title_bar_is_highlight = want_focus || (window_to_highlight && window->RootWindowForTitleBarHighlight == window_to_highlight->RootWindowForTitleBarHighlight); - const bool handle_borders_and_resize_grips = true; // This exists to facilitate merge with 'docking' branch. + const bool title_bar_is_highlight = want_focus || (window_to_highlight && (window->RootWindowForTitleBarHighlight == window_to_highlight->RootWindowForTitleBarHighlight || (window->DockNode && window->DockNode == window_to_highlight->DockNode))); RenderWindowDecorations(window, title_bar_rect, title_bar_is_highlight, handle_borders_and_resize_grips, resize_grip_count, resize_grip_col, resize_grip_draw_size); if (render_decorations_in_parent) @@ -7090,7 +8104,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.MenuBarAppending = false; window->DC.MenuColumns.Update(style.ItemSpacing.x, window_just_activated_by_user); window->DC.TreeDepth = 0; - window->DC.TreeJumpToParentOnPopMask = 0x00; + window->DC.TreeHasStackDataDepthMask = 0x00; window->DC.ChildWindows.resize(0); window->DC.StateStorage = &window->StateStorage; window->DC.CurrentColumns = NULL; @@ -7109,6 +8123,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (window->AutoFitFramesY > 0) window->AutoFitFramesY--; + // Clear SetNextWindowXXX data (can aim to move this higher in the function) + g.NextWindowData.ClearFlags(); + // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there) // We ImGuiFocusRequestFlags_UnlessBelowModal to: // - Avoid focusing a window that is created outside of a modal. This will prevent active modal from being closed. @@ -7118,22 +8135,46 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (want_focus && window == g.NavWindow) NavInitWindow(window, false); // <-- this is in the way for us to be able to defer and sort reappearing FocusWindow() calls + // Close requested by platform window (apply to all windows in this viewport) + if (p_open != NULL && window->Viewport->PlatformRequestClose && window->Viewport != GetMainViewport()) + { + IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Window '%s' closed by PlatformRequestClose\n", window->Name); + *p_open = false; + g.NavWindowingToggleLayer = false; // Assume user mapped PlatformRequestClose on ALT-F4 so we disable ALT for menu toggle. False positive not an issue. // FIXME-NAV: Try removing. + } + + // Pressing CTRL+C copy window content into the clipboard + // [EXPERIMENTAL] Breaks on nested Begin/End pairs. We need to work that out and add better logging scope. + // [EXPERIMENTAL] Text outputs has many issues. + if (g.IO.ConfigWindowsCopyContentsWithCtrlC) + if (g.NavWindow && g.NavWindow->RootWindow == window && g.ActiveId == 0 && Shortcut(ImGuiMod_Ctrl | ImGuiKey_C)) + LogToClipboard(0); + // Title bar - if (!(flags & ImGuiWindowFlags_NoTitleBar)) + if (!(flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive) RenderWindowTitleBarContents(window, ImRect(title_bar_rect.Min.x + window->WindowBorderSize, title_bar_rect.Min.y, title_bar_rect.Max.x - window->WindowBorderSize, title_bar_rect.Max.y), name, p_open); + else if (!(flags & ImGuiWindowFlags_NoTitleBar) && window->DockIsActive) + LogText("%.*s\n", (int)(FindRenderedTextEnd(window->Name) - window->Name), window->Name); // Clear hit test shape every frame window->HitTestHoleSize.x = window->HitTestHoleSize.y = 0; - // Pressing CTRL+C while holding on a window copy its content to the clipboard - // This works but 1. doesn't handle multiple Begin/End pairs, 2. recursing into another Begin/End pair - so we need to work that out and add better logging scope. - // Maybe we can support CTRL+C on every element? - /* - //if (g.NavWindow == window && g.ActiveId == 0) - if (g.ActiveId == window->MoveId) - if (g.IO.KeyCtrl && IsKeyPressed(ImGuiKey_C)) - LogToClipboard(); - */ + if (flags & ImGuiWindowFlags_Tooltip) + g.TooltipPreviousWindow = window; + + if (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable) + { + // Docking: Dragging a dockable window (or any of its child) turns it into a drag and drop source. + // We need to do this _before_ we overwrite window->DC.LastItemId below because BeginDockableDragDropSource() also overwrites it. + if (g.MovingWindow == window && (window->RootWindowDockTree->Flags & ImGuiWindowFlags_NoDocking) == 0) + BeginDockableDragDropSource(window); + + // Docking: Any dockable window can act as a target. For dock node hosts we call BeginDockableDragDropTarget() in DockNodeUpdate() instead. + if (g.DragDropActive && !(flags & ImGuiWindowFlags_NoDocking)) + if (g.MovingWindow == NULL || g.MovingWindow->RootWindowDockTree != window) + if ((window == window->RootWindowDockTree) && !(window->Flags & ImGuiWindowFlags_DockNodeHost)) + BeginDockableDragDropTarget(window); + } // We fill last item data based on Title Bar/Tab, in order for IsItemHovered() and IsItemActive() to be usable after Begin(). // This is useful to allow creating context menus on title bar only, etc. @@ -7155,30 +8196,43 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { // Skip refresh always mark active if (window->SkipRefresh) - window->Active = true; + SetWindowActiveForSkipRefresh(window); // Append + SetCurrentViewport(window, window->Viewport); SetCurrentWindow(window); + g.NextWindowData.ClearFlags(); SetLastItemDataForWindow(window, window->TitleBarRect()); } - if (!window->SkipRefresh) + if (!(flags & ImGuiWindowFlags_DockNodeHost) && !window->SkipRefresh) PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true); // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default "Debug" window is unused) window->WriteAccessed = false; window->BeginCount++; - g.NextWindowData.ClearFlags(); // Update visibility if (first_begin_of_the_frame && !window->SkipRefresh) { + // When we are about to select this tab (which will only be visible on the _next frame_), flag it with a non-zero HiddenFramesCannotSkipItems. + // This will have the important effect of actually returning true in Begin() and not setting SkipItems, allowing an earlier submission of the window contents. + // This is analogous to regular windows being hidden from one frame. + // It is especially important as e.g. nested TabBars would otherwise generate flicker in the form of one empty frame, or focus requests won't be processed. + if (window->DockIsActive && !window->DockTabIsVisible) + { + if (window->LastFrameJustFocused == g.FrameCount) + window->HiddenFramesCannotSkipItems = 1; + else + window->HiddenFramesCanSkipItems = 1; + } + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_ChildMenu)) { // Child window can be out of sight and have "negative" clip windows. // Mark them as collapsed so commands are skipped earlier (we can't manually collapse them because they have no title bar). - IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0); - const bool nav_request = (flags & ImGuiWindowFlags_NavFlattened) && (g.NavAnyRequest && g.NavWindow && g.NavWindow->RootWindowForNav == window->RootWindowForNav); + IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0 || window->DockIsActive); + const bool nav_request = (window->ChildFlags & ImGuiChildFlags_NavFlattened) && (g.NavAnyRequest && g.NavWindow && g.NavWindow->RootWindowForNav == window->RootWindowForNav); if (!g.LogEnabled && !nav_request) if (window->OuterRectClipped.Min.x >= window->OuterRectClipped.Max.x || window->OuterRectClipped.Min.y >= window->OuterRectClipped.Max.y) { @@ -7216,6 +8270,16 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && window->HiddenFramesCannotSkipItems <= 0) skip_items = true; window->SkipItems = skip_items; + + // Restore NavLayersActiveMaskNext to previous value when not visible, so a CTRL+Tab back can use a safe value. + if (window->SkipItems) + window->DC.NavLayersActiveMaskNext = window->DC.NavLayersActiveMask; + + // Sanity check: there are two spots which can set Appearing = true + // - when 'window_just_activated_by_user' is set -> HiddenFramesCannotSkipItems is set -> SkipItems always false + // - in BeginDocked() path when DockNodeIsVisible == DockTabIsVisible == true -> hidden _should_ be all zero // FIXME: Not formally proven, hence the assert. + if (window->SkipItems && !window->Appearing) + IM_ASSERT(window->Appearing == false); // Please report on GitHub if this triggers: https://github.com/ocornut/imgui/issues/4177 } else if (first_begin_of_the_frame) { @@ -7241,7 +8305,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) static void ImGui::SetLastItemDataForWindow(ImGuiWindow* window, const ImRect& rect) { ImGuiContext& g = *GImGui; - SetLastItemData(window->MoveId, g.CurrentItemFlags, IsMouseHoveringRect(rect.Min, rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, rect); + if (window->DockIsActive) + SetLastItemData(window->MoveId, g.CurrentItemFlags, window->DockTabItemStatusFlags, window->DockTabItemRect); + else + SetLastItemData(window->MoveId, g.CurrentItemFlags, IsMouseHoveringRect(rect.Min, rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, rect); } void ImGui::End() @@ -7258,14 +8325,14 @@ void ImGui::End() ImGuiWindowStackData& window_stack_data = g.CurrentWindowStack.back(); // Error checking: verify that user doesn't directly call End() on a child window. - if (window->Flags & ImGuiWindowFlags_ChildWindow) + if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_DockNodeHost) && !window->DockIsActive) IM_ASSERT_USER_ERROR(g.WithinEndChild, "Must call EndChild() and not End()!"); // Close anything that is open if (window->DC.CurrentColumns) EndColumns(); - if (!window->SkipRefresh) - PopClipRect(); // Inner window clip rectangle + if (!(window->Flags & ImGuiWindowFlags_DockNodeHost) && !window->SkipRefresh) // Pop inner window clip rectangle + PopClipRect(); PopFocusScope(); if (window_stack_data.DisabledOverrideReenable && window->RootWindow == window) EndDisabledOverrideReenable(); @@ -7277,21 +8344,32 @@ void ImGui::End() } // Stop logging - if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging + if (g.LogWindow == window) // FIXME: add more options for scope of logging LogFinish(); if (window->DC.IsSetPos) ErrorCheckUsingSetCursorPosToExtendParentBoundaries(); + // Docking: report contents sizes to parent to allow for auto-resize + if (window->DockNode && window->DockTabIsVisible) + if (ImGuiWindow* host_window = window->DockNode->HostWindow) // FIXME-DOCK + host_window->DC.CursorMaxPos = window->DC.CursorMaxPos + window->WindowPadding - host_window->WindowPadding; + // Pop from window stack g.LastItemData = window_stack_data.ParentLastItemDataBackup; if (window->Flags & ImGuiWindowFlags_ChildMenu) g.BeginMenuDepth--; if (window->Flags & ImGuiWindowFlags_Popup) g.BeginPopupStack.pop_back(); - window_stack_data.StackSizesOnBegin.CompareWithContextState(&g); + + // Error handling, state recovery + if (g.IO.ConfigErrorRecovery) + ErrorRecoveryTryToRecoverWindowState(&window_stack_data.StackSizesInBegin); + g.CurrentWindowStack.pop_back(); SetCurrentWindow(g.CurrentWindowStack.Size == 0 ? NULL : g.CurrentWindowStack.back().Window); + if (g.CurrentWindow) + SetCurrentViewport(g.CurrentWindow, g.CurrentWindow->Viewport); } void ImGui::BringWindowToFocusFront(ImGuiWindow* window) @@ -7319,7 +8397,7 @@ void ImGui::BringWindowToDisplayFront(ImGuiWindow* window) { ImGuiContext& g = *GImGui; ImGuiWindow* current_front_window = g.Windows.back(); - if (current_front_window == window || current_front_window->RootWindow == window) // Cheap early out (could be better) + if (current_front_window == window || current_front_window->RootWindowDockTree == window) // Cheap early out (could be better) return; for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the top-most window if (g.Windows[i] == window) @@ -7399,7 +8477,7 @@ void ImGui::FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags) if (g.NavWindow != window) { SetNavWindow(window); - if (window && g.NavDisableMouseHover) + if (window && g.NavHighlightItemUnderNav) g.NavMousePosDirty = true; g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId g.NavLayer = ImGuiNavLayer_Main; @@ -7412,31 +8490,39 @@ void ImGui::FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags) } // Move the root window to the top of the pile - IM_ASSERT(window == NULL || window->RootWindow != NULL); - ImGuiWindow* focus_front_window = window ? window->RootWindow : NULL; // NB: In docking branch this is window->RootWindowDockStop - ImGuiWindow* display_front_window = window ? window->RootWindow : NULL; + IM_ASSERT(window == NULL || window->RootWindowDockTree != NULL); + ImGuiWindow* focus_front_window = window ? window->RootWindow : NULL; + ImGuiWindow* display_front_window = window ? window->RootWindowDockTree : NULL; + ImGuiDockNode* dock_node = window ? window->DockNode : NULL; + bool active_id_window_is_dock_node_host = (g.ActiveIdWindow && dock_node && dock_node->HostWindow == g.ActiveIdWindow); // Steal active widgets. Some of the cases it triggers includes: // - Focus a window while an InputText in another window is active, if focus happens before the old InputText can run. // - When using Nav to activate menu items (due to timing of activating on press->new window appears->losing ActiveId) + // - Using dock host items (tab, collapse button) can trigger this before we redirect the ActiveIdWindow toward the child window. if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != focus_front_window) - if (!g.ActiveIdNoClearOnFocusLoss) + if (!g.ActiveIdNoClearOnFocusLoss && !active_id_window_is_dock_node_host) ClearActiveID(); // Passing NULL allow to disable keyboard focus if (!window) return; + window->LastFrameJustFocused = g.FrameCount; + + // Select in dock node + // For #2304 we avoid applying focus immediately before the tabbar is visible. + //if (dock_node && dock_node->TabBar) + // dock_node->TabBar->SelectedTabId = dock_node->TabBar->NextSelectedTabId = window->TabId; // Bring to front BringWindowToFocusFront(focus_front_window); - if (((window->Flags | display_front_window->Flags) & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0) + if (((window->Flags | focus_front_window->Flags | display_front_window->Flags) & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0) BringWindowToDisplayFront(display_front_window); } void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window, ImGuiViewport* filter_viewport, ImGuiFocusRequestFlags flags) { ImGuiContext& g = *GImGui; - IM_UNUSED(filter_viewport); // Unused in master branch. int start_idx = g.WindowsFocusOrder.Size - 1; if (under_this_window != NULL) { @@ -7455,8 +8541,15 @@ void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWind ImGuiWindow* window = g.WindowsFocusOrder[i]; if (window == ignore_window || !window->WasActive) continue; + if (filter_viewport != NULL && window->Viewport != filter_viewport) + continue; if ((window->Flags & (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) != (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) { + // FIXME-DOCK: When ImGuiFocusRequestFlags_RestoreFocusedChild is set... + // This is failing (lagging by one frame) for docked windows. + // If A and B are docked into window and B disappear, at the NewFrame() call site window->NavLastChildNavWindow will still point to B. + // We might leverage the tab order implicitly stored in window->DockNodeAsHost->TabBar (essentially the 'most_recently_selected_tab' code in tab bar will do that but on next update) + // to tell which is the "previous" window. Or we may leverage 'LastFrameFocused/LastFrameJustFocused' and have this function handle child window itself? FocusWindow(window, flags); return; } @@ -7473,30 +8566,45 @@ void ImGui::SetCurrentFont(ImFont* font) g.Font = font; g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale); g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; + g.FontScale = g.FontSize / g.Font->FontSize; ImFontAtlas* atlas = g.Font->ContainerAtlas; g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; g.DrawListSharedData.TexUvLines = atlas->TexUvLines; g.DrawListSharedData.Font = g.Font; g.DrawListSharedData.FontSize = g.FontSize; + g.DrawListSharedData.FontScale = g.FontScale; } +// Use ImDrawList::_SetTextureID(), making our shared g.FontStack[] authorative against window-local ImDrawList. +// - Whereas ImDrawList::PushTextureID()/PopTextureID() is not to be used across Begin() calls. +// - Note that we don't propagate current texture id when e.g. Begin()-ing into a new window, we never really did... +// - Some code paths never really fully worked with multiple atlas textures. +// - The right-ish solution may be to remove _SetTextureID() and make AddText/RenderText lazily call PushTextureID()/PopTextureID() +// the same way AddImage() does, but then all other primitives would also need to? I don't think we should tackle this problem +// because we have a concrete need and a test bed for multiple atlas textures. void ImGui::PushFont(ImFont* font) { ImGuiContext& g = *GImGui; - if (!font) + if (font == NULL) font = GetDefaultFont(); - SetCurrentFont(font); g.FontStack.push_back(font); - g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID); + SetCurrentFont(font); + g.CurrentWindow->DrawList->_SetTextureID(font->ContainerAtlas->TexID); } void ImGui::PopFont() { ImGuiContext& g = *GImGui; - g.CurrentWindow->DrawList->PopTextureID(); + if (g.FontStack.Size <= 0) + { + IM_ASSERT_USER_ERROR(0, "Calling PopFont() too many times!"); + return; + } g.FontStack.pop_back(); - SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back()); + ImFont* font = g.FontStack.Size == 0 ? GetDefaultFont() : g.FontStack.back(); + SetCurrentFont(font); + g.CurrentWindow->DrawList->_SetTextureID(font->ContainerAtlas->TexID); } void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) @@ -7515,7 +8623,11 @@ void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) void ImGui::PopItemFlag() { ImGuiContext& g = *GImGui; - IM_ASSERT(g.ItemFlagsStack.Size > 1); // Too many calls to PopItemFlag() - we always leave a 0 at the bottom of the stack. + if (g.ItemFlagsStack.Size <= 1) + { + IM_ASSERT_USER_ERROR(0, "Calling PopItemFlag() too many times!"); + return; + } g.ItemFlagsStack.pop_back(); g.CurrentItemFlags = g.ItemFlagsStack.back(); } @@ -7524,8 +8636,9 @@ void ImGui::PopItemFlag() // - Those can be nested but it cannot be used to enable an already disabled section (a single BeginDisabled(true) in the stack is enough to keep everything disabled) // - Visually this is currently altering alpha, but it is expected that in a future styling system this would work differently. // - Feedback welcome at https://github.com/ocornut/imgui/issues/211 -// - BeginDisabled(false) essentially does nothing useful but is provided to facilitate use of boolean expressions. If you can avoid calling BeginDisabled(False)/EndDisabled() best to avoid it. -// - Optimized shortcuts instead of PushStyleVar() + PushItemFlag() +// - BeginDisabled(false)/EndDisabled() essentially does nothing but is provided to facilitate use of boolean expressions. +// (as a micro-optimization: if you have tens of thousands of BeginDisabled(false)/EndDisabled() pairs, you might want to reformulate your code to avoid making those calls) +// - Note: mixing up BeginDisabled() and PushItemFlag(ImGuiItemFlags_Disabled) is currently NOT SUPPORTED. void ImGui::BeginDisabled(bool disabled) { ImGuiContext& g = *GImGui; @@ -7544,7 +8657,11 @@ void ImGui::BeginDisabled(bool disabled) void ImGui::EndDisabled() { ImGuiContext& g = *GImGui; - IM_ASSERT(g.DisabledStackSize > 0); + if (g.DisabledStackSize <= 0) + { + IM_ASSERT_USER_ERROR(0, "Calling EndDisabled() too many times!"); + return; + } g.DisabledStackSize--; bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; //PopItemFlag(); @@ -7577,41 +8694,28 @@ void ImGui::EndDisabledOverrideReenable() g.Style.Alpha = g.DisabledAlphaBackup * g.Style.DisabledAlpha; } -void ImGui::PushTabStop(bool tab_stop) -{ - PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); -} - -void ImGui::PopTabStop() -{ - PopItemFlag(); -} - -void ImGui::PushButtonRepeat(bool repeat) -{ - PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); -} - -void ImGui::PopButtonRepeat() -{ - PopItemFlag(); -} - void ImGui::PushTextWrapPos(float wrap_pos_x) { - ImGuiWindow* window = GetCurrentWindow(); + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; window->DC.TextWrapPosStack.push_back(window->DC.TextWrapPos); window->DC.TextWrapPos = wrap_pos_x; } void ImGui::PopTextWrapPos() { - ImGuiWindow* window = GetCurrentWindow(); + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->DC.TextWrapPosStack.Size <= 0) + { + IM_ASSERT_USER_ERROR(0, "Calling PopTextWrapPos() too many times!"); + return; + } window->DC.TextWrapPos = window->DC.TextWrapPosStack.back(); window->DC.TextWrapPosStack.pop_back(); } -static ImGuiWindow* GetCombinedRootWindow(ImGuiWindow* window, bool popup_hierarchy) +static ImGuiWindow* GetCombinedRootWindow(ImGuiWindow* window, bool popup_hierarchy, bool dock_hierarchy) { ImGuiWindow* last_window = NULL; while (last_window != window) @@ -7620,13 +8724,15 @@ static ImGuiWindow* GetCombinedRootWindow(ImGuiWindow* window, bool popup_hierar window = window->RootWindow; if (popup_hierarchy) window = window->RootWindowPopupTree; - } + if (dock_hierarchy) + window = window->RootWindowDockTree; + } return window; } -bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent, bool popup_hierarchy) +bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent, bool popup_hierarchy, bool dock_hierarchy) { - ImGuiWindow* window_root = GetCombinedRootWindow(window, popup_hierarchy); + ImGuiWindow* window_root = GetCombinedRootWindow(window, popup_hierarchy, dock_hierarchy); if (window_root == potential_parent) return true; while (window != NULL) @@ -7679,9 +8785,9 @@ bool ImGui::IsWindowAbove(ImGuiWindow* potential_above, ImGuiWindow* potential_b // Refer to FAQ entry "How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?" for details. bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) { - IM_ASSERT((flags & ~ImGuiHoveredFlags_AllowedMaskForIsWindowHovered) == 0 && "Invalid flags for IsWindowHovered()!"); - ImGuiContext& g = *GImGui; + IM_ASSERT_USER_ERROR((flags & ~ImGuiHoveredFlags_AllowedMaskForIsWindowHovered) == 0, "Invalid flags for IsWindowHovered()!"); + ImGuiWindow* ref_window = g.HoveredWindow; ImGuiWindow* cur_window = g.CurrentWindow; if (ref_window == NULL) @@ -7691,12 +8797,13 @@ bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) { IM_ASSERT(cur_window); // Not inside a Begin()/End() const bool popup_hierarchy = (flags & ImGuiHoveredFlags_NoPopupHierarchy) == 0; + const bool dock_hierarchy = (flags & ImGuiHoveredFlags_DockHierarchy) != 0; if (flags & ImGuiHoveredFlags_RootWindow) - cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy); + cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy, dock_hierarchy); bool result; if (flags & ImGuiHoveredFlags_ChildWindows) - result = IsWindowChildOf(ref_window, cur_window, popup_hierarchy); + result = IsWindowChildOf(ref_window, cur_window, popup_hierarchy, dock_hierarchy); else result = (ref_window == cur_window); if (!result) @@ -7735,15 +8842,28 @@ bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) IM_ASSERT(cur_window); // Not inside a Begin()/End() const bool popup_hierarchy = (flags & ImGuiFocusedFlags_NoPopupHierarchy) == 0; + const bool dock_hierarchy = (flags & ImGuiFocusedFlags_DockHierarchy) != 0; if (flags & ImGuiHoveredFlags_RootWindow) - cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy); + cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy, dock_hierarchy); if (flags & ImGuiHoveredFlags_ChildWindows) - return IsWindowChildOf(ref_window, cur_window, popup_hierarchy); + return IsWindowChildOf(ref_window, cur_window, popup_hierarchy, dock_hierarchy); else return (ref_window == cur_window); } +ImGuiID ImGui::GetWindowDockID() +{ + ImGuiContext& g = *GImGui; + return g.CurrentWindow->DockId; +} + +bool ImGui::IsWindowDocked() +{ + ImGuiContext& g = *GImGui; + return g.CurrentWindow->DockIsActive; +} + // Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext) // Note that NoNavFocus makes the window not reachable with CTRL+TAB but it can still be focused with mouse or programmatically. // If you want a window to never be focused, you may use the e.g. NoInputs flag. @@ -7788,6 +8908,7 @@ void ImGui::SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond) if (offset.x == 0.0f && offset.y == 0.0f) return; MarkIniSettingsDirty(window); + // FIXME: share code with TranslateWindow(), need to confirm whether the 3 rect modified by TranslateWindow() are desirable here. window->DC.CursorPos += offset; // As we happen to move the window while it is being appended to (which is a bad idea - will smear) let's at least offset the cursor window->DC.CursorMaxPos += offset; // And more importantly we need to offset CursorMaxPos/CursorStartPos this so ContentSize calculation doesn't get affected. window->DC.IdealMaxPos += offset; @@ -7925,6 +9046,7 @@ void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pi g.NextWindowData.PosVal = pos; g.NextWindowData.PosPivotVal = pivot; g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always; + g.NextWindowData.PosUndock = true; } void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond) @@ -7987,6 +9109,29 @@ void ImGui::SetNextWindowBgAlpha(float alpha) g.NextWindowData.BgAlphaVal = alpha; } +void ImGui::SetNextWindowViewport(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasViewport; + g.NextWindowData.ViewportId = id; +} + +void ImGui::SetNextWindowDockID(ImGuiID id, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasDock; + g.NextWindowData.DockCond = cond ? cond : ImGuiCond_Always; + g.NextWindowData.DockId = id; +} + +void ImGui::SetNextWindowClass(const ImGuiWindowClass* window_class) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT((window_class->ViewportFlagsOverrideSet & window_class->ViewportFlagsOverrideClear) == 0); // Cannot set both set and clear for the same bit + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasWindowClass; + g.NextWindowData.WindowClass = *window_class; +} + // This is experimental and meant to be a toy for exploring a future/wider range of features. void ImGui::SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags) { @@ -8001,6 +9146,19 @@ ImDrawList* ImGui::GetWindowDrawList() return window->DrawList; } +float ImGui::GetWindowDpiScale() +{ + ImGuiContext& g = *GImGui; + return g.CurrentDpiScale; +} + +ImGuiViewport* ImGui::GetWindowViewport() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.CurrentViewport != NULL && g.CurrentViewport == g.CurrentWindow->Viewport); + return g.CurrentViewport; +} + ImFont* ImGui::GetFont() { return GImGui->Font; @@ -8023,6 +9181,7 @@ void ImGui::SetWindowFontScale(float scale) ImGuiWindow* window = GetCurrentWindow(); window->FontWindowScale = scale; g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); + g.FontScale = g.DrawListSharedData.FontScale = g.FontSize / g.Font->FontSize; } void ImGui::PushFocusScope(ImGuiID id) @@ -8038,9 +9197,9 @@ void ImGui::PushFocusScope(ImGuiID id) void ImGui::PopFocusScope() { ImGuiContext& g = *GImGui; - if (g.FocusScopeStack.Size == 0) + if (g.FocusScopeStack.Size <= g.StackSizesInBeginForCurrentWindow->SizeOfFocusScopeStack) { - IM_ASSERT_USER_ERROR(g.FocusScopeStack.Size > 0, "Calling PopFocusScope() too many times!"); + IM_ASSERT_USER_ERROR(0, "Calling PopFocusScope() too many times!"); return; } g.FocusScopeStack.pop_back(); @@ -8086,7 +9245,7 @@ void ImGui::FocusItem() return; } - ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_FocusApi | ImGuiNavMoveFlags_NoSetNavHighlight | ImGuiNavMoveFlags_NoSelect; + ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_FocusApi | ImGuiNavMoveFlags_NoSetNavCursorVisible | ImGuiNavMoveFlags_NoSelect; ImGuiScrollFlags scroll_flags = window->Appearing ? ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_AlwaysCenterY : ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleEdgeY; SetNavWindow(window); NavMoveRequestSubmit(ImGuiDir_None, ImGuiDir_Up, move_flags, scroll_flags); @@ -8121,7 +9280,7 @@ void ImGui::SetKeyboardFocusHere(int offset) SetNavWindow(window); - ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_Activate | ImGuiNavMoveFlags_FocusApi | ImGuiNavMoveFlags_NoSetNavHighlight; + ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_Activate | ImGuiNavMoveFlags_FocusApi | ImGuiNavMoveFlags_NoSetNavCursorVisible; ImGuiScrollFlags scroll_flags = window->Appearing ? ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_AlwaysCenterY : ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleEdgeY; NavMoveRequestSubmit(ImGuiDir_None, offset < 0 ? ImGuiDir_Up : ImGuiDir_Down, move_flags, scroll_flags); // FIXME-NAV: Once we refactor tabbing, add LegacyApi flag to not activate non-inputable. if (offset == -1) @@ -8183,6 +9342,7 @@ bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) // This is one of the very rare legacy case where we use ImGuiWindow methods, // it should ideally be flattened at some point but it's been used a lots by widgets. +IM_MSVC_RUNTIME_CHECKS_OFF ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) { ImGuiID seed = IDStack.back(); @@ -8220,6 +9380,16 @@ ImGuiID ImGuiWindow::GetID(int n) } // This is only used in rare/specific situations to manufacture an ID out of nowhere. +// FIXME: Consider instead storing last non-zero ID + count of successive zero-ID, and combine those? +ImGuiID ImGuiWindow::GetIDFromPos(const ImVec2& p_abs) +{ + ImGuiID seed = IDStack.back(); + ImVec2 p_rel = ImGui::WindowPosAbsToRel(this, p_abs); + ImGuiID id = ImHashData(&p_rel, sizeof(p_rel), seed); + return id; +} + +// " ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs) { ImGuiID seed = IDStack.back(); @@ -8300,7 +9470,11 @@ ImGuiID ImGui::GetIDWithSeed(int n, ImGuiID seed) void ImGui::PopID() { ImGuiWindow* window = GImGui->CurrentWindow; - IM_ASSERT(window->IDStack.Size > 1); // Too many PopID(), or could be popping in a wrong/different window? + if (window->IDStack.Size <= 1) + { + IM_ASSERT_USER_ERROR(0, "Calling PopID() too many times!"); + return; + } window->IDStack.pop_back(); } @@ -8322,10 +9496,17 @@ ImGuiID ImGui::GetID(const void* ptr_id) return window->GetID(ptr_id); } +ImGuiID ImGui::GetID(int int_id) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->GetID(int_id); +} +IM_MSVC_RUNTIME_CHECKS_RESTORE + //----------------------------------------------------------------------------- // [SECTION] INPUTS //----------------------------------------------------------------------------- -// - GetModForModKey() [Internal] +// - GetModForLRModKey() [Internal] // - FixupKeyChord() [Internal] // - GetKeyData() [Internal] // - GetKeyIndex() [Internal] @@ -8388,7 +9569,7 @@ ImGuiID ImGui::GetID(const void* ptr_id) // - Shortcut() [Internal] //----------------------------------------------------------------------------- -static ImGuiKeyChord GetModForModKey(ImGuiKey key) +static ImGuiKeyChord GetModForLRModKey(ImGuiKey key) { if (key == ImGuiKey_LeftCtrl || key == ImGuiKey_RightCtrl) return ImGuiMod_Ctrl; @@ -8405,8 +9586,8 @@ ImGuiKeyChord ImGui::FixupKeyChord(ImGuiKeyChord key_chord) { // Add ImGuiMod_XXXX when a corresponding ImGuiKey_LeftXXX/ImGuiKey_RightXXX is specified. ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); - if (IsModKey(key)) - key_chord |= GetModForModKey(key); + if (IsLRModKey(key)) + key_chord |= GetModForLRModKey(key); return key_chord; } @@ -8418,26 +9599,9 @@ ImGuiKeyData* ImGui::GetKeyData(ImGuiContext* ctx, ImGuiKey key) if (key & ImGuiMod_Mask_) key = ConvertSingleModFlagToKey(key); -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - IM_ASSERT(key >= ImGuiKey_LegacyNativeKey_BEGIN && key < ImGuiKey_NamedKey_END); - if (IsLegacyKey(key) && g.IO.KeyMap[key] != -1) - key = (ImGuiKey)g.IO.KeyMap[key]; // Remap native->imgui or imgui->native -#else IM_ASSERT(IsNamedKey(key) && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend & user code."); -#endif - return &g.IO.KeysData[key - ImGuiKey_KeysData_OFFSET]; -} - -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO -// Formally moved to obsolete section in 1.90.5 in spite of documented as obsolete since 1.87 -ImGuiKey ImGui::GetKeyIndex(ImGuiKey key) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(IsNamedKey(key)); - const ImGuiKeyData* key_data = GetKeyData(key); - return (ImGuiKey)(key_data - g.IO.KeysData); + return &g.IO.KeysData[key - ImGuiKey_NamedKey_BEGIN]; } -#endif // Those names a provided for debugging purpose and are not meant to be saved persistently not compared. static const char* const GKeyNames[] = @@ -8470,18 +9634,7 @@ const char* ImGui::GetKeyName(ImGuiKey key) { if (key == ImGuiKey_None) return "None"; -#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO IM_ASSERT(IsNamedKeyOrMod(key) && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend and user code."); -#else - ImGuiContext& g = *GImGui; - if (IsLegacyKey(key)) - { - if (g.IO.KeyMap[key] == -1) - return "N/A"; - IM_ASSERT(IsNamedKey((ImGuiKey)g.IO.KeyMap[key])); - key = (ImGuiKey)g.IO.KeyMap[key]; - } -#endif if (key & ImGuiMod_Mask_) key = ConvertSingleModFlagToKey(key); if (!IsNamedKey(key)) @@ -8497,8 +9650,8 @@ const char* ImGui::GetKeyChordName(ImGuiKeyChord key_chord) ImGuiContext& g = *GImGui; const ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); - if (IsModKey(key)) - key_chord &= ~GetModForModKey(key); // Return "Ctrl+LeftShift" instead of "Ctrl+Shift+LeftShift" + if (IsLRModKey(key)) + key_chord &= ~GetModForLRModKey(key); // Return "Ctrl+LeftShift" instead of "Ctrl+Shift+LeftShift" ImFormatString(g.TempKeychordName, IM_ARRAYSIZE(g.TempKeychordName), "%s%s%s%s%s", (key_chord & ImGuiMod_Ctrl) ? "Ctrl+" : "", (key_chord & ImGuiMod_Shift) ? "Shift+" : "", @@ -8698,8 +9851,9 @@ static int CalcRoutingScore(ImGuiID focus_scope_id, ImGuiID owner_id, ImGuiInput return 0; } -// We need this to filter some Shortcut() routes when an item e.g. an InputText() is active -// e.g. ImGuiKey_G won't be considered a shortcut when item is active, but ImGuiMod|ImGuiKey_G can be. +// - We need this to filter some Shortcut() routes when an item e.g. an InputText() is active +// e.g. ImGuiKey_G won't be considered a shortcut when item is active, but ImGuiMod|ImGuiKey_G can be. +// - This is also used by UpdateInputEvents() to avoid trickling in the most common case of e.g. pressing ImGuiKey_G also emitting a G character. static bool IsKeyChordPotentiallyCharInput(ImGuiKeyChord key_chord) { // Mimic 'ignore_char_inputs' logic in InputText() @@ -8713,6 +9867,8 @@ static bool IsKeyChordPotentiallyCharInput(ImGuiKeyChord key_chord) // Return true for A-Z, 0-9 and other keys associated to char inputs. Other keys such as F1-F12 won't be filtered. ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); + if (key == ImGuiKey_None) + return false; return g.KeysMayBeCharInput.TestBit(key); } @@ -8836,7 +9992,7 @@ bool ImGui::IsKeyPressed(ImGuiKey key, bool repeat) return IsKeyPressed(key, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None, ImGuiKeyOwner_Any); } -// Important: unless legacy IsKeyPressed(ImGuiKey, bool repeat=true) which DEFAULT to repeat, this requires EXPLICIT repeat. +// Important: unlike legacy IsKeyPressed(ImGuiKey, bool repeat=true) which DEFAULT to repeat, this requires EXPLICIT repeat. bool ImGui::IsKeyPressed(ImGuiKey key, ImGuiInputFlags flags, ImGuiID owner_id) { const ImGuiKeyData* key_data = GetKeyData(key); @@ -8982,6 +10138,8 @@ bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool c // Hit testing, expanded for touch input if (!rect_clipped.ContainsWithPad(g.IO.MousePos, g.Style.TouchExtraPadding)) return false; + if (!g.MouseViewport->GetMainRect().Overlaps(rect_clipped)) + return false; return true; } @@ -9086,6 +10244,9 @@ ImGuiMouseCursor ImGui::GetMouseCursor() return g.MouseCursor; } +// We intentionally accept values of ImGuiMouseCursor that are outside our bounds, in case users needs to hack-in a custom cursor value. +// Custom cursors may be handled by custom backends. If you are using a standard backend and want to hack in a custom cursor, you may +// handle it before the backend _NewFrame() call and temporarily set ImGuiConfigFlags_NoMouseCursorChange during the backend _NewFrame() call. void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type) { ImGuiContext& g = *GImGui; @@ -9116,73 +10277,8 @@ static void ImGui::UpdateKeyboardInputs() ImGuiContext& g = *GImGui; ImGuiIO& io = g.IO; - // Import legacy keys or verify they are not used -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - if (io.BackendUsingLegacyKeyArrays == 0) - { - // Backend used new io.AddKeyEvent() API: Good! Verify that old arrays are never written to externally. - for (int n = 0; n < ImGuiKey_LegacyNativeKey_END; n++) - IM_ASSERT((io.KeysDown[n] == false || IsKeyDown((ImGuiKey)n)) && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!"); - } - else - { - if (g.FrameCount == 0) - for (int n = ImGuiKey_LegacyNativeKey_BEGIN; n < ImGuiKey_LegacyNativeKey_END; n++) - IM_ASSERT(g.IO.KeyMap[n] == -1 && "Backend is not allowed to write to io.KeyMap[0..511]!"); - - // Build reverse KeyMap (Named -> Legacy) - for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_NamedKey_END; n++) - if (io.KeyMap[n] != -1) - { - IM_ASSERT(IsLegacyKey((ImGuiKey)io.KeyMap[n])); - io.KeyMap[io.KeyMap[n]] = n; - } - - // Import legacy keys into new ones - for (int n = ImGuiKey_LegacyNativeKey_BEGIN; n < ImGuiKey_LegacyNativeKey_END; n++) - if (io.KeysDown[n] || io.BackendUsingLegacyKeyArrays == 1) - { - const ImGuiKey key = (ImGuiKey)(io.KeyMap[n] != -1 ? io.KeyMap[n] : n); - IM_ASSERT(io.KeyMap[n] == -1 || IsNamedKey(key)); - io.KeysData[key].Down = io.KeysDown[n]; - if (key != n) - io.KeysDown[key] = io.KeysDown[n]; // Allow legacy code using io.KeysDown[GetKeyIndex()] with old backends - io.BackendUsingLegacyKeyArrays = 1; - } - if (io.BackendUsingLegacyKeyArrays == 1) - { - GetKeyData(ImGuiMod_Ctrl)->Down = io.KeyCtrl; - GetKeyData(ImGuiMod_Shift)->Down = io.KeyShift; - GetKeyData(ImGuiMod_Alt)->Down = io.KeyAlt; - GetKeyData(ImGuiMod_Super)->Down = io.KeySuper; - } - } -#endif - - // Import legacy ImGuiNavInput_ io inputs and convert to gamepad keys -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; - if (io.BackendUsingLegacyNavInputArray && nav_gamepad_active) - { - #define MAP_LEGACY_NAV_INPUT_TO_KEY1(_KEY, _NAV1) do { io.KeysData[_KEY].Down = (io.NavInputs[_NAV1] > 0.0f); io.KeysData[_KEY].AnalogValue = io.NavInputs[_NAV1]; } while (0) - #define MAP_LEGACY_NAV_INPUT_TO_KEY2(_KEY, _NAV1, _NAV2) do { io.KeysData[_KEY].Down = (io.NavInputs[_NAV1] > 0.0f) || (io.NavInputs[_NAV2] > 0.0f); io.KeysData[_KEY].AnalogValue = ImMax(io.NavInputs[_NAV1], io.NavInputs[_NAV2]); } while (0) - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceDown, ImGuiNavInput_Activate); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceRight, ImGuiNavInput_Cancel); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceLeft, ImGuiNavInput_Menu); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceUp, ImGuiNavInput_Input); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadLeft, ImGuiNavInput_DpadLeft); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadRight, ImGuiNavInput_DpadRight); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadUp, ImGuiNavInput_DpadUp); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadDown, ImGuiNavInput_DpadDown); - MAP_LEGACY_NAV_INPUT_TO_KEY2(ImGuiKey_GamepadL1, ImGuiNavInput_FocusPrev, ImGuiNavInput_TweakSlow); - MAP_LEGACY_NAV_INPUT_TO_KEY2(ImGuiKey_GamepadR1, ImGuiNavInput_FocusNext, ImGuiNavInput_TweakFast); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickLeft, ImGuiNavInput_LStickLeft); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickRight, ImGuiNavInput_LStickRight); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickUp, ImGuiNavInput_LStickUp); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickDown, ImGuiNavInput_LStickDown); - #undef NAV_MAP_KEY - } -#endif + if (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard) + io.ClearInputKeys(); // Update aliases for (int n = 0; n < ImGuiMouseButton_COUNT; n++) @@ -9207,22 +10303,21 @@ static void ImGui::UpdateKeyboardInputs() // Clear gamepad data if disabled if ((io.BackendFlags & ImGuiBackendFlags_HasGamepad) == 0) - for (int i = ImGuiKey_Gamepad_BEGIN; i < ImGuiKey_Gamepad_END; i++) + for (int key = ImGuiKey_Gamepad_BEGIN; key < ImGuiKey_Gamepad_END; key++) { - io.KeysData[i - ImGuiKey_KeysData_OFFSET].Down = false; - io.KeysData[i - ImGuiKey_KeysData_OFFSET].AnalogValue = 0.0f; + io.KeysData[key - ImGuiKey_NamedKey_BEGIN].Down = false; + io.KeysData[key - ImGuiKey_NamedKey_BEGIN].AnalogValue = 0.0f; } // Update keys - for (int i = 0; i < ImGuiKey_KeysData_SIZE; i++) + for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key++) { - ImGuiKeyData* key_data = &io.KeysData[i]; + ImGuiKeyData* key_data = &io.KeysData[key - ImGuiKey_NamedKey_BEGIN]; key_data->DownDurationPrev = key_data->DownDuration; key_data->DownDuration = key_data->Down ? (key_data->DownDuration < 0.0f ? 0.0f : key_data->DownDuration + io.DeltaTime) : -1.0f; if (key_data->DownDuration == 0.0f) { - ImGuiKey key = (ImGuiKey)(ImGuiKey_KeysData_OFFSET + i); - if (IsKeyboardKey(key)) + if (IsKeyboardKey((ImGuiKey)key)) g.LastKeyboardKeyPressTime = g.Time; else if (key == ImGuiKey_ReservedForModCtrl || key == ImGuiKey_ReservedForModShift || key == ImGuiKey_ReservedForModAlt || key == ImGuiKey_ReservedForModSuper) g.LastKeyboardKeyPressTime = g.Time; @@ -9232,7 +10327,7 @@ static void ImGui::UpdateKeyboardInputs() // Update keys/input owner (named keys only): one entry per key for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { - ImGuiKeyData* key_data = &io.KeysData[key - ImGuiKey_KeysData_OFFSET]; + ImGuiKeyData* key_data = &io.KeysData[key - ImGuiKey_NamedKey_BEGIN]; ImGuiKeyOwnerData* owner_data = &g.KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN]; owner_data->OwnerCurr = owner_data->OwnerNext; if (!key_data->Down) // Important: ownership is released on the frame after a release. Ensure a 'MouseDown -> CloseWindow -> MouseUp' chain doesn't lead to someone else seeing the MouseUp. @@ -9273,9 +10368,9 @@ static void ImGui::UpdateMouseInputs() g.MouseStationaryTimer = mouse_stationary ? (g.MouseStationaryTimer + io.DeltaTime) : 0.0f; //IMGUI_DEBUG_LOG("%.4f\n", g.MouseStationaryTimer); - // If mouse moved we re-enable mouse hovering in case it was disabled by gamepad/keyboard. In theory should use a >0.0f threshold but would need to reset in everywhere we set this to true. + // If mouse moved we re-enable mouse hovering in case it was disabled by keyboard/gamepad. In theory should use a >0.0f threshold but would need to reset in everywhere we set this to true. if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) - g.NavDisableMouseHover = false; + g.NavHighlightItemUnderNav = false; for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) { @@ -9300,21 +10395,24 @@ static void ImGui::UpdateMouseInputs() io.MouseClickedTime[i] = g.Time; io.MouseClickedPos[i] = io.MousePos; io.MouseClickedCount[i] = io.MouseClickedLastCount[i]; + io.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f); io.MouseDragMaxDistanceSqr[i] = 0.0f; } else if (io.MouseDown[i]) { // Maintain the maximum distance we reaching from the initial click position, which is used with dragging threshold - float delta_sqr_click_pos = IsMousePosValid(&io.MousePos) ? ImLengthSqr(io.MousePos - io.MouseClickedPos[i]) : 0.0f; - io.MouseDragMaxDistanceSqr[i] = ImMax(io.MouseDragMaxDistanceSqr[i], delta_sqr_click_pos); + ImVec2 delta_from_click_pos = IsMousePosValid(&io.MousePos) ? (io.MousePos - io.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f); + io.MouseDragMaxDistanceSqr[i] = ImMax(io.MouseDragMaxDistanceSqr[i], ImLengthSqr(delta_from_click_pos)); + io.MouseDragMaxDistanceAbs[i].x = ImMax(io.MouseDragMaxDistanceAbs[i].x, delta_from_click_pos.x < 0.0f ? -delta_from_click_pos.x : delta_from_click_pos.x); + io.MouseDragMaxDistanceAbs[i].y = ImMax(io.MouseDragMaxDistanceAbs[i].y, delta_from_click_pos.y < 0.0f ? -delta_from_click_pos.y : delta_from_click_pos.y); } // We provide io.MouseDoubleClicked[] as a legacy service io.MouseDoubleClicked[i] = (io.MouseClickedCount[i] == 2); - // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation + // Clicking any mouse button reactivate mouse hovering which may have been deactivated by keyboard/gamepad navigation if (io.MouseClicked[i]) - g.NavDisableMouseHover = false; + g.NavHighlightItemUnderNav = false; } } @@ -9496,6 +10594,7 @@ static void DebugPrintInputEvent(const char* prefix, const ImGuiInputEvent* e) if (e->Type == ImGuiInputEventType_MousePos) { if (e->MousePos.PosX == -FLT_MAX && e->MousePos.PosY == -FLT_MAX) IMGUI_DEBUG_LOG_IO("[io] %s: MousePos (-FLT_MAX, -FLT_MAX)\n", prefix); else IMGUI_DEBUG_LOG_IO("[io] %s: MousePos (%.1f, %.1f) (%s)\n", prefix, e->MousePos.PosX, e->MousePos.PosY, GetMouseSourceName(e->MousePos.MouseSource)); return; } if (e->Type == ImGuiInputEventType_MouseButton) { IMGUI_DEBUG_LOG_IO("[io] %s: MouseButton %d %s (%s)\n", prefix, e->MouseButton.Button, e->MouseButton.Down ? "Down" : "Up", GetMouseSourceName(e->MouseButton.MouseSource)); return; } if (e->Type == ImGuiInputEventType_MouseWheel) { IMGUI_DEBUG_LOG_IO("[io] %s: MouseWheel (%.3f, %.3f) (%s)\n", prefix, e->MouseWheel.WheelX, e->MouseWheel.WheelY, GetMouseSourceName(e->MouseWheel.MouseSource)); return; } + if (e->Type == ImGuiInputEventType_MouseViewport){IMGUI_DEBUG_LOG_IO("[io] %s: MouseViewport (0x%08X)\n", prefix, e->MouseViewport.HoveredViewportID); return; } if (e->Type == ImGuiInputEventType_Key) { IMGUI_DEBUG_LOG_IO("[io] %s: Key \"%s\" %s\n", prefix, ImGui::GetKeyName(e->Key.Key), e->Key.Down ? "Down" : "Up"); return; } if (e->Type == ImGuiInputEventType_Text) { IMGUI_DEBUG_LOG_IO("[io] %s: Text: %c (U+%08X)\n", prefix, e->Text.Char, e->Text.Char); return; } if (e->Type == ImGuiInputEventType_Focus) { IMGUI_DEBUG_LOG_IO("[io] %s: AppFocused %d\n", prefix, e->AppFocused.Focused); return; } @@ -9514,11 +10613,11 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) // Only trickle chars<>key when working with InputText() // FIXME: InputText() could parse event trail? // FIXME: Could specialize chars<>keys trickling rules for control keys (those not typically associated to characters) - const bool trickle_interleaved_keys_and_text = (trickle_fast_inputs && g.WantTextInputNextFrame == 1); + const bool trickle_interleaved_nonchar_keys_and_text = (trickle_fast_inputs && g.WantTextInputNextFrame == 1); - bool mouse_moved = false, mouse_wheeled = false, key_changed = false, text_inputted = false; + bool mouse_moved = false, mouse_wheeled = false, key_changed = false, key_changed_nonchar = false, text_inputted = false; int mouse_button_changed = 0x00; - ImBitArray key_changed_mask; + ImBitArray key_changed_mask; int event_n = 0; for (; event_n < g.InputEventsQueue.Size; event_n++) @@ -9559,35 +10658,45 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) io.MouseSource = e->MouseWheel.MouseSource; mouse_wheeled = true; } + else if (e->Type == ImGuiInputEventType_MouseViewport) + { + io.MouseHoveredViewport = e->MouseViewport.HoveredViewportID; + } else if (e->Type == ImGuiInputEventType_Key) { // Trickling Rule: Stop processing queued events if we got multiple action on the same button + if (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard) + continue; ImGuiKey key = e->Key.Key; IM_ASSERT(key != ImGuiKey_None); ImGuiKeyData* key_data = GetKeyData(key); const int key_data_index = (int)(key_data - g.IO.KeysData); - if (trickle_fast_inputs && key_data->Down != e->Key.Down && (key_changed_mask.TestBit(key_data_index) || text_inputted || mouse_button_changed != 0)) + if (trickle_fast_inputs && key_data->Down != e->Key.Down && (key_changed_mask.TestBit(key_data_index) || mouse_button_changed != 0)) + break; + + const bool key_is_potentially_for_char_input = IsKeyChordPotentiallyCharInput(GetMergedModsFromKeys() | key); + if (trickle_interleaved_nonchar_keys_and_text && (text_inputted && !key_is_potentially_for_char_input)) break; + key_data->Down = e->Key.Down; key_data->AnalogValue = e->Key.AnalogValue; key_changed = true; key_changed_mask.SetBit(key_data_index); - - // Allow legacy code using io.KeysDown[GetKeyIndex()] with new backends -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - io.KeysDown[key_data_index] = key_data->Down; - if (io.KeyMap[key_data_index] != -1) - io.KeysDown[io.KeyMap[key_data_index]] = key_data->Down; -#endif + if (trickle_interleaved_nonchar_keys_and_text && !key_is_potentially_for_char_input) + key_changed_nonchar = true; } else if (e->Type == ImGuiInputEventType_Text) { + if (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard) + continue; // Trickling Rule: Stop processing queued events if keys/mouse have been interacted with - if (trickle_fast_inputs && ((key_changed && trickle_interleaved_keys_and_text) || mouse_button_changed != 0 || mouse_moved || mouse_wheeled)) + if (trickle_fast_inputs && (mouse_button_changed != 0 || mouse_moved || mouse_wheeled)) + break; + if (trickle_interleaved_nonchar_keys_and_text && key_changed_nonchar) break; unsigned int c = e->Text.Char; io.InputQueueCharacters.push_back(c <= IM_UNICODE_CODEPOINT_MAX ? (ImWchar)c : IM_UNICODE_CODEPOINT_INVALID); - if (trickle_interleaved_keys_and_text) + if (trickle_interleaved_nonchar_keys_and_text) text_inputted = true; } else if (e->Type == ImGuiInputEventType_Focus) @@ -9626,7 +10735,10 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) // - we clear in EndFrame() and not now in order allow application/user code polling this flag // (e.g. custom backend may want to clear additional data, custom widgets may want to react with a "canceling" event). if (g.IO.AppFocusLost) + { g.IO.ClearInputKeys(); + g.IO.ClearInputMouse(); + } } ImGuiID ImGui::GetKeyOwner(ImGuiKey key) @@ -9729,6 +10841,11 @@ void ImGui::SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags) } } +void ImGui::SetItemKeyOwner(ImGuiKey key) +{ + SetItemKeyOwner(key, ImGuiInputFlags_None); +} + // This is the only public API until we expose owner_id versions of the API as replacements. bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord) { @@ -9756,17 +10873,20 @@ bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiInputFlags flags, Im void ImGui::SetNextItemShortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags) { ImGuiContext& g = *GImGui; - g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasShortcut; + g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasShortcut; g.NextItemData.Shortcut = key_chord; g.NextItemData.ShortcutFlags = flags; } +// Called from within ItemAdd: at this point we can read from NextItemData and write to LastItemData void ImGui::ItemHandleShortcut(ImGuiID id) { ImGuiContext& g = *GImGui; ImGuiInputFlags flags = g.NextItemData.ShortcutFlags; IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetNextItemShortcut) == 0); // Passing flags not supported by SetNextItemShortcut()! + if (g.LastItemData.ItemFlags & ImGuiItemFlags_Disabled) + return; if (flags & ImGuiInputFlags_Tooltip) { g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasShortcut; @@ -9790,7 +10910,7 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags) bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id) { - //ImGuiContext& g = *GImGui; + ImGuiContext& g = *GImGui; //IMGUI_DEBUG_LOG("Shortcut(%s, flags=%X, owner_id=0x%08X)\n", GetKeyChordName(key_chord, g.TempBuffer.Data, g.TempBuffer.Size), flags, owner_id); // When using (owner_id == 0/Any): SetShortcutRouting() will use CurrentFocusScopeId and filter with this, so IsKeyPressed() is fine with he 0/Any. @@ -9802,6 +10922,9 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID own if (owner_id == ImGuiKeyOwner_Any || owner_id == ImGuiKeyOwner_NoOwner) owner_id = GetRoutingIdFromOwnerId(owner_id); + if (g.CurrentItemFlags & ImGuiItemFlags_Disabled) + return false; + // Submit route if (!SetShortcutRouting(key_chord, flags, owner_id)) return false; @@ -9823,7 +10946,16 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID own //----------------------------------------------------------------------------- -// [SECTION] ERROR CHECKING +// [SECTION] ERROR CHECKING, STATE RECOVERY +//----------------------------------------------------------------------------- +// - DebugCheckVersionAndDataLayout() (called via IMGUI_CHECKVERSION() macros) +// - ErrorCheckUsingSetCursorPosToExtendParentBoundaries() +// - ErrorCheckNewFrameSanityChecks() +// - ErrorCheckEndFrameSanityChecks() +// - ErrorRecoveryStoreState() +// - ErrorRecoveryTryToRecoverState() +// - ErrorRecoveryTryToRecoverWindowState() +// - ErrorLog() //----------------------------------------------------------------------------- // Verify ABI compatibility between caller code and compiled version of Dear ImGui. This helps detects some build issues. @@ -9913,121 +11045,182 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() IM_ASSERT(g.Style.WindowMinSize.x >= 1.0f && g.Style.WindowMinSize.y >= 1.0f && "Invalid style setting."); IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right); IM_ASSERT(g.Style.ColorButtonPosition == ImGuiDir_Left || g.Style.ColorButtonPosition == ImGuiDir_Right); -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_COUNT; n++) - IM_ASSERT(g.IO.KeyMap[n] >= -1 && g.IO.KeyMap[n] < ImGuiKey_LegacyNativeKey_END && "io.KeyMap[] contains an out of bound value (need to be 0..511, or -1 for unmapped key)"); - // Check: required key mapping (we intentionally do NOT check all keys to not pressure user into setting up everything, but Space is required and was only added in 1.60 WIP) - if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && g.IO.BackendUsingLegacyKeyArrays == 1) - IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation."); + // Error handling: we do not accept 100% silent recovery! Please contact me if you feel this is getting in your way. + if (g.IO.ConfigErrorRecovery) + IM_ASSERT(g.IO.ConfigErrorRecoveryEnableAssert || g.IO.ConfigErrorRecoveryEnableDebugLog || g.IO.ConfigErrorRecoveryEnableTooltip || g.ErrorCallback != NULL); + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // Remap legacy names + if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) + { + g.IO.ConfigNavMoveSetMousePos = true; + g.IO.ConfigFlags &= ~ImGuiConfigFlags_NavEnableSetMousePos; + } + if (g.IO.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard) + { + g.IO.ConfigNavCaptureKeyboard = false; + g.IO.ConfigFlags &= ~ImGuiConfigFlags_NavNoCaptureKeyboard; + } + + // Remap legacy clipboard handlers (OBSOLETED in 1.91.1, August 2024) + if (g.IO.GetClipboardTextFn != NULL && (g.PlatformIO.Platform_GetClipboardTextFn == NULL || g.PlatformIO.Platform_GetClipboardTextFn == Platform_GetClipboardTextFn_DefaultImpl)) + g.PlatformIO.Platform_GetClipboardTextFn = [](ImGuiContext* ctx) { return ctx->IO.GetClipboardTextFn(ctx->IO.ClipboardUserData); }; + if (g.IO.SetClipboardTextFn != NULL && (g.PlatformIO.Platform_SetClipboardTextFn == NULL || g.PlatformIO.Platform_SetClipboardTextFn == Platform_SetClipboardTextFn_DefaultImpl)) + g.PlatformIO.Platform_SetClipboardTextFn = [](ImGuiContext* ctx, const char* text) { return ctx->IO.SetClipboardTextFn(ctx->IO.ClipboardUserData, text); }; #endif + + // Perform simple check: error if Docking or Viewport are enabled _exactly_ on frame 1 (instead of frame 0 or later), which is a common error leading to loss of .ini data. + if (g.FrameCount == 1 && (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable) && (g.ConfigFlagsLastFrame & ImGuiConfigFlags_DockingEnable) == 0) + IM_ASSERT(0 && "Please set DockingEnable before the first call to NewFrame()! Otherwise you will lose your .ini settings!"); + if (g.FrameCount == 1 && (g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) && (g.ConfigFlagsLastFrame & ImGuiConfigFlags_ViewportsEnable) == 0) + IM_ASSERT(0 && "Please set ViewportsEnable before the first call to NewFrame()! Otherwise you will lose your .ini settings!"); + + // Perform simple checks: multi-viewport and platform windows support + if (g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + if ((g.IO.BackendFlags & ImGuiBackendFlags_PlatformHasViewports) && (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasViewports)) + { + IM_ASSERT((g.FrameCount == 0 || g.FrameCount == g.FrameCountPlatformEnded) && "Forgot to call UpdatePlatformWindows() in main loop after EndFrame()? Check examples/ applications for reference."); + IM_ASSERT(g.PlatformIO.Platform_CreateWindow != NULL && "Platform init didn't install handlers?"); + IM_ASSERT(g.PlatformIO.Platform_DestroyWindow != NULL && "Platform init didn't install handlers?"); + IM_ASSERT(g.PlatformIO.Platform_GetWindowPos != NULL && "Platform init didn't install handlers?"); + IM_ASSERT(g.PlatformIO.Platform_SetWindowPos != NULL && "Platform init didn't install handlers?"); + IM_ASSERT(g.PlatformIO.Platform_GetWindowSize != NULL && "Platform init didn't install handlers?"); + IM_ASSERT(g.PlatformIO.Platform_SetWindowSize != NULL && "Platform init didn't install handlers?"); + IM_ASSERT(g.PlatformIO.Monitors.Size > 0 && "Platform init didn't setup Monitors list?"); + IM_ASSERT((g.Viewports[0]->PlatformUserData != NULL || g.Viewports[0]->PlatformHandle != NULL) && "Platform init didn't setup main viewport."); + if (g.IO.ConfigDockingTransparentPayload && (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable)) + IM_ASSERT(g.PlatformIO.Platform_SetWindowAlpha != NULL && "Platform_SetWindowAlpha handler is required to use io.ConfigDockingTransparent!"); + } + else + { + // Disable feature, our backends do not support it + g.IO.ConfigFlags &= ~ImGuiConfigFlags_ViewportsEnable; + } + + // Perform simple checks on platform monitor data + compute a total bounding box for quick early outs + for (ImGuiPlatformMonitor& mon : g.PlatformIO.Monitors) + { + IM_UNUSED(mon); + IM_ASSERT(mon.MainSize.x > 0.0f && mon.MainSize.y > 0.0f && "Monitor main bounds not setup properly."); + IM_ASSERT(ImRect(mon.MainPos, mon.MainPos + mon.MainSize).Contains(ImRect(mon.WorkPos, mon.WorkPos + mon.WorkSize)) && "Monitor work bounds not setup properly. If you don't have work area information, just copy MainPos/MainSize into them."); + IM_ASSERT(mon.DpiScale > 0.0f && mon.DpiScale < 99.0f && "Monitor DpiScale is invalid."); // Typical correct values would be between 1.0f and 4.0f + } + } } static void ImGui::ErrorCheckEndFrameSanityChecks() { - ImGuiContext& g = *GImGui; - // Verify that io.KeyXXX fields haven't been tampered with. Key mods should not be modified between NewFrame() and EndFrame() // One possible reason leading to this assert is that your backends update inputs _AFTER_ NewFrame(). // It is known that when some modal native windows called mid-frame takes focus away, some backends such as GLFW will // send key release events mid-frame. This would normally trigger this assertion and lead to sheared inputs. // We silently accommodate for this case by ignoring the case where all io.KeyXXX modifiers were released (aka key_mod_flags == 0), // while still correctly asserting on mid-frame key press events. + ImGuiContext& g = *GImGui; const ImGuiKeyChord key_mods = GetMergedModsFromKeys(); + IM_UNUSED(g); + IM_UNUSED(key_mods); IM_ASSERT((key_mods == 0 || g.IO.KeyMods == key_mods) && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); IM_UNUSED(key_mods); - // [EXPERIMENTAL] Recover from errors: You may call this yourself before EndFrame(). - //ErrorCheckEndFrameRecover(); - - // Report when there is a mismatch of Begin/BeginChild vs End/EndChild calls. Important: Remember that the Begin/BeginChild API requires you - // to always call End/EndChild even if Begin/BeginChild returns false! (this is unfortunately inconsistent with most other Begin* API). - if (g.CurrentWindowStack.Size != 1) - { - if (g.CurrentWindowStack.Size > 1) - { - ImGuiWindow* window = g.CurrentWindowStack.back().Window; // <-- This window was not Ended! - IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size == 1, "Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?"); - IM_UNUSED(window); - while (g.CurrentWindowStack.Size > 1) - End(); - } - else - { - IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size == 1, "Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?"); - } - } + IM_ASSERT(g.CurrentWindowStack.Size == 1); + IM_ASSERT(g.CurrentWindowStack[0].Window->IsFallbackWindow); +} - IM_ASSERT_USER_ERROR(g.GroupStack.Size == 0, "Missing EndGroup call!"); +// Save current stack sizes. Called e.g. by NewFrame() and by Begin() but may be called for manual recovery. +void ImGui::ErrorRecoveryStoreState(ImGuiErrorRecoveryState* state_out) +{ + ImGuiContext& g = *GImGui; + state_out->SizeOfWindowStack = (short)g.CurrentWindowStack.Size; + state_out->SizeOfIDStack = (short)g.CurrentWindow->IDStack.Size; + state_out->SizeOfTreeStack = (short)g.CurrentWindow->DC.TreeDepth; // NOT g.TreeNodeStack.Size which is a partial stack! + state_out->SizeOfColorStack = (short)g.ColorStack.Size; + state_out->SizeOfStyleVarStack = (short)g.StyleVarStack.Size; + state_out->SizeOfFontStack = (short)g.FontStack.Size; + state_out->SizeOfFocusScopeStack = (short)g.FocusScopeStack.Size; + state_out->SizeOfGroupStack = (short)g.GroupStack.Size; + state_out->SizeOfItemFlagsStack = (short)g.ItemFlagsStack.Size; + state_out->SizeOfBeginPopupStack = (short)g.BeginPopupStack.Size; + state_out->SizeOfDisabledStack = (short)g.DisabledStackSize; } -// Experimental recovery from incorrect usage of BeginXXX/EndXXX/PushXXX/PopXXX calls. -// Must be called during or before EndFrame(). -// This is generally flawed as we are not necessarily End/Popping things in the right order. -// FIXME: Can't recover from inside BeginTabItem/EndTabItem yet. -// FIXME: Can't recover from interleaved BeginTabBar/Begin -void ImGui::ErrorCheckEndFrameRecover(ImGuiErrorLogCallback log_callback, void* user_data) +// Chosen name "Try to recover" over e.g. "Restore" to suggest this is not a 100% guaranteed recovery. +// Called by e.g. EndFrame() but may be called for manual recovery. +// Attempt to recover full window stack. +void ImGui::ErrorRecoveryTryToRecoverState(const ImGuiErrorRecoveryState* state_in) { // PVS-Studio V1044 is "Loop break conditions do not depend on the number of iterations" ImGuiContext& g = *GImGui; - while (g.CurrentWindowStack.Size > 0) //-V1044 + while (g.CurrentWindowStack.Size > state_in->SizeOfWindowStack) //-V1044 { - ErrorCheckEndWindowRecover(log_callback, user_data); + // Recap: + // - Begin()/BeginChild() return false to indicate the window is collapsed or fully clipped. + // - Always call a matching End() for each Begin() call, regardless of its return value! + // - Begin/End and BeginChild/EndChild logic is KNOWN TO BE INCONSISTENT WITH ALL OTHER BEGIN/END FUNCTIONS. + // - We will fix that in a future major update. ImGuiWindow* window = g.CurrentWindow; - if (g.CurrentWindowStack.Size == 1) - { - IM_ASSERT(window->IsFallbackWindow); - break; - } if (window->Flags & ImGuiWindowFlags_ChildWindow) { - if (log_callback) log_callback(user_data, "Recovered from missing EndChild() for '%s'", window->Name); + IM_ASSERT_USER_ERROR(0, "Missing EndChild()"); EndChild(); } else { - if (log_callback) log_callback(user_data, "Recovered from missing End() for '%s'", window->Name); + IM_ASSERT_USER_ERROR(0, "Missing End()"); End(); } } + if (g.CurrentWindowStack.Size == state_in->SizeOfWindowStack) + ErrorRecoveryTryToRecoverWindowState(state_in); } -// Must be called before End()/EndChild() -void ImGui::ErrorCheckEndWindowRecover(ImGuiErrorLogCallback log_callback, void* user_data) +// Called by e.g. End() but may be called for manual recovery. +// Read '// Error Handling [BETA]' block in imgui_internal.h for details. +// Attempt to recover from incorrect usage of BeginXXX/EndXXX/PushXXX/PopXXX calls. +void ImGui::ErrorRecoveryTryToRecoverWindowState(const ImGuiErrorRecoveryState* state_in) { ImGuiContext& g = *GImGui; - while (g.CurrentTable && (g.CurrentTable->OuterWindow == g.CurrentWindow || g.CurrentTable->InnerWindow == g.CurrentWindow)) + + while (g.CurrentTable != NULL && g.CurrentTable->InnerWindow == g.CurrentWindow) //-V1044 { - if (log_callback) log_callback(user_data, "Recovered from missing EndTable() in '%s'", g.CurrentTable->OuterWindow->Name); + IM_ASSERT_USER_ERROR(0, "Missing EndTable()"); EndTable(); } ImGuiWindow* window = g.CurrentWindow; - ImGuiStackSizes* stack_sizes = &g.CurrentWindowStack.back().StackSizesOnBegin; - IM_ASSERT(window != NULL); - while (g.CurrentTabBar != NULL) //-V1044 + + // FIXME: Can't recover from inside BeginTabItem/EndTabItem yet. + while (g.CurrentTabBar != NULL && g.CurrentTabBar->Window == window) //-V1044 { - if (log_callback) log_callback(user_data, "Recovered from missing EndTabBar() in '%s'", window->Name); + IM_ASSERT_USER_ERROR(0, "Missing EndTabBar()"); EndTabBar(); } - while (window->DC.TreeDepth > 0) + while (g.CurrentMultiSelect != NULL && g.CurrentMultiSelect->Storage->Window == window) //-V1044 + { + IM_ASSERT_USER_ERROR(0, "Missing EndMultiSelect()"); + EndMultiSelect(); + } + while (window->DC.TreeDepth > state_in->SizeOfTreeStack) //-V1044 { - if (log_callback) log_callback(user_data, "Recovered from missing TreePop() in '%s'", window->Name); + IM_ASSERT_USER_ERROR(0, "Missing TreePop()"); TreePop(); } - while (g.GroupStack.Size > stack_sizes->SizeOfGroupStack) //-V1044 + while (g.GroupStack.Size > state_in->SizeOfGroupStack) //-V1044 { - if (log_callback) log_callback(user_data, "Recovered from missing EndGroup() in '%s'", window->Name); + IM_ASSERT_USER_ERROR(0, "Missing EndGroup()"); EndGroup(); } - while (window->IDStack.Size > 1) + IM_ASSERT(g.GroupStack.Size == state_in->SizeOfGroupStack); + while (window->IDStack.Size > state_in->SizeOfIDStack) //-V1044 { - if (log_callback) log_callback(user_data, "Recovered from missing PopID() in '%s'", window->Name); + IM_ASSERT_USER_ERROR(0, "Missing PopID()"); PopID(); } - while (g.DisabledStackSize > stack_sizes->SizeOfDisabledStack) //-V1044 + while (g.DisabledStackSize > state_in->SizeOfDisabledStack) //-V1044 { - if (log_callback) log_callback(user_data, "Recovered from missing EndDisabled() in '%s'", window->Name); + IM_ASSERT_USER_ERROR(0, "Missing EndDisabled()"); if (g.CurrentItemFlags & ImGuiItemFlags_Disabled) EndDisabled(); else @@ -10036,70 +11229,151 @@ void ImGui::ErrorCheckEndWindowRecover(ImGuiErrorLogCallback log_callback, vo g.CurrentWindowStack.back().DisabledOverrideReenable = false; } } - while (g.ColorStack.Size > stack_sizes->SizeOfColorStack) + IM_ASSERT(g.DisabledStackSize == state_in->SizeOfDisabledStack); + while (g.ColorStack.Size > state_in->SizeOfColorStack) //-V1044 { - if (log_callback) log_callback(user_data, "Recovered from missing PopStyleColor() in '%s' for ImGuiCol_%s", window->Name, GetStyleColorName(g.ColorStack.back().Col)); + IM_ASSERT_USER_ERROR(0, "Missing PopStyleColor()"); PopStyleColor(); } - while (g.ItemFlagsStack.Size > stack_sizes->SizeOfItemFlagsStack) //-V1044 + while (g.ItemFlagsStack.Size > state_in->SizeOfItemFlagsStack) //-V1044 { - if (log_callback) log_callback(user_data, "Recovered from missing PopItemFlag() in '%s'", window->Name); + IM_ASSERT_USER_ERROR(0, "Missing PopItemFlag()"); PopItemFlag(); } - while (g.StyleVarStack.Size > stack_sizes->SizeOfStyleVarStack) //-V1044 + while (g.StyleVarStack.Size > state_in->SizeOfStyleVarStack) //-V1044 { - if (log_callback) log_callback(user_data, "Recovered from missing PopStyleVar() in '%s'", window->Name); + IM_ASSERT_USER_ERROR(0, "Missing PopStyleVar()"); PopStyleVar(); } - while (g.FontStack.Size > stack_sizes->SizeOfFontStack) //-V1044 + while (g.FontStack.Size > state_in->SizeOfFontStack) //-V1044 { - if (log_callback) log_callback(user_data, "Recovered from missing PopFont() in '%s'", window->Name); + IM_ASSERT_USER_ERROR(0, "Missing PopFont()"); PopFont(); } - while (g.FocusScopeStack.Size > stack_sizes->SizeOfFocusScopeStack + 1) //-V1044 + while (g.FocusScopeStack.Size > state_in->SizeOfFocusScopeStack) //-V1044 { - if (log_callback) log_callback(user_data, "Recovered from missing PopFocusScope() in '%s'", window->Name); + IM_ASSERT_USER_ERROR(0, "Missing PopFocusScope()"); PopFocusScope(); } + //IM_ASSERT(g.FocusScopeStack.Size == state_in->SizeOfFocusScopeStack); } -// Save current stack sizes for later compare -void ImGuiStackSizes::SetToContextState(ImGuiContext* ctx) +bool ImGui::ErrorLog(const char* msg) { - ImGuiContext& g = *ctx; + ImGuiContext& g = *GImGui; + + // Output to debug log +#ifndef IMGUI_DISABLE_DEBUG_TOOLS ImGuiWindow* window = g.CurrentWindow; - SizeOfIDStack = (short)window->IDStack.Size; - SizeOfColorStack = (short)g.ColorStack.Size; - SizeOfStyleVarStack = (short)g.StyleVarStack.Size; - SizeOfFontStack = (short)g.FontStack.Size; - SizeOfFocusScopeStack = (short)g.FocusScopeStack.Size; - SizeOfGroupStack = (short)g.GroupStack.Size; - SizeOfItemFlagsStack = (short)g.ItemFlagsStack.Size; - SizeOfBeginPopupStack = (short)g.BeginPopupStack.Size; - SizeOfDisabledStack = (short)g.DisabledStackSize; + + if (g.IO.ConfigErrorRecoveryEnableDebugLog) + { + if (g.ErrorFirst) + IMGUI_DEBUG_LOG_ERROR("[imgui-error] (current settings: Assert=%d, Log=%d, Tooltip=%d)\n", + g.IO.ConfigErrorRecoveryEnableAssert, g.IO.ConfigErrorRecoveryEnableDebugLog, g.IO.ConfigErrorRecoveryEnableTooltip); + IMGUI_DEBUG_LOG_ERROR("[imgui-error] In window '%s': %s\n", window ? window->Name : "NULL", msg); + } + g.ErrorFirst = false; + + // Output to tooltip + if (g.IO.ConfigErrorRecoveryEnableTooltip) + { + if (g.WithinFrameScope && BeginErrorTooltip()) + { + if (g.ErrorCountCurrentFrame < 20) + { + Text("In window '%s': %s", window ? window->Name : "NULL", msg); + if (window && (!window->IsFallbackWindow || window->WasActive)) + GetForegroundDrawList(window)->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 0, 0, 255)); + } + if (g.ErrorCountCurrentFrame == 20) + Text("(and more errors)"); + // EndFrame() will amend debug buttons to this window, after all errors have been submitted. + EndErrorTooltip(); + } + g.ErrorCountCurrentFrame++; + } +#endif + + // Output to callback + if (g.ErrorCallback != NULL) + g.ErrorCallback(&g, g.ErrorCallbackUserData, msg); + + // Return whether we should assert + return g.IO.ConfigErrorRecoveryEnableAssert; } -// Compare to detect usage errors -void ImGuiStackSizes::CompareWithContextState(ImGuiContext* ctx) +void ImGui::ErrorCheckEndFrameFinalizeErrorTooltip() { - ImGuiContext& g = *ctx; - ImGuiWindow* window = g.CurrentWindow; - IM_UNUSED(window); +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + ImGuiContext& g = *GImGui; + if (g.DebugDrawIdConflicts != 0 && g.IO.KeyCtrl == false) + g.DebugDrawIdConflictsCount = g.HoveredIdPreviousFrameItemCount; + if (g.DebugDrawIdConflicts != 0 && g.DebugItemPickerActive == false && BeginErrorTooltip()) + { + Text("Programmer error: %d visible items with conflicting ID!", g.DebugDrawIdConflictsCount); + BulletText("Code should use PushID()/PopID() in loops, or append \"##xx\" to same-label identifiers!"); + BulletText("Empty label e.g. Button(\"\") == same ID as parent widget/node. Use Button(\"##xx\") instead!"); + //BulletText("Code intending to use duplicate ID may use e.g. PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag()"); // Not making this too visible for fear of it being abused. + BulletText("Set io.ConfigDebugHighlightIdConflicts=false to disable this warning in non-programmers builds."); + Separator(); + Text("(Hold CTRL to: use"); + SameLine(); + if (SmallButton("Item Picker")) + DebugStartItemPicker(); + SameLine(); + Text("to break in item call-stack, or"); + SameLine(); + if (SmallButton("Open FAQ->About ID Stack System") && g.PlatformIO.Platform_OpenInShellFn != NULL) + g.PlatformIO.Platform_OpenInShellFn(&g, "https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#qa-usage"); + EndErrorTooltip(); + } + + if (g.ErrorCountCurrentFrame > 0 && BeginErrorTooltip()) // Amend at end of frame + { + Separator(); + Text("(Hold CTRL to:"); + SameLine(); + if (SmallButton("Enable Asserts")) + g.IO.ConfigErrorRecoveryEnableAssert = true; + //SameLine(); + //if (SmallButton("Hide Error Tooltips")) + // g.IO.ConfigErrorRecoveryEnableTooltip = false; // Too dangerous + SameLine(0, 0); + Text(")"); + EndErrorTooltip(); + } +#endif +} - // Window stacks - // NOT checking: DC.ItemWidth, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin) - IM_ASSERT(SizeOfIDStack == window->IDStack.Size && "PushID/PopID or TreeNode/TreePop Mismatch!"); +// Pseudo-tooltip. Follow mouse until CTRL is held. When CTRL is held we lock position, allowing to click it. +bool ImGui::BeginErrorTooltip() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = FindWindowByName("##Tooltip_Error"); + const bool use_locked_pos = (g.IO.KeyCtrl && window && window->WasActive); + PushStyleColor(ImGuiCol_PopupBg, ImLerp(g.Style.Colors[ImGuiCol_PopupBg], ImVec4(1.0f, 0.0f, 0.0f, 1.0f), 0.15f)); + if (use_locked_pos) + SetNextWindowPos(g.ErrorTooltipLockedPos); + bool is_visible = Begin("##Tooltip_Error", NULL, ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize); + PopStyleColor(); + if (is_visible && g.CurrentWindow->BeginCount == 1) + { + SeparatorText("MESSAGE FROM DEAR IMGUI"); + BringWindowToDisplayFront(g.CurrentWindow); + BringWindowToFocusFront(g.CurrentWindow); + g.ErrorTooltipLockedPos = GetWindowPos(); + } + else if (!is_visible) + { + End(); + } + return is_visible; +} - // Global stacks - // For color, style and font stacks there is an incentive to use Push/Begin/Pop/.../End patterns, so we relax our checks a little to allow them. - IM_ASSERT(SizeOfGroupStack == g.GroupStack.Size && "BeginGroup/EndGroup Mismatch!"); - IM_ASSERT(SizeOfBeginPopupStack == g.BeginPopupStack.Size && "BeginPopup/EndPopup or BeginMenu/EndMenu Mismatch!"); - IM_ASSERT(SizeOfDisabledStack == g.DisabledStackSize && "BeginDisabled/EndDisabled Mismatch!"); - IM_ASSERT(SizeOfItemFlagsStack >= g.ItemFlagsStack.Size && "PushItemFlag/PopItemFlag Mismatch!"); - IM_ASSERT(SizeOfColorStack >= g.ColorStack.Size && "PushStyleColor/PopStyleColor Mismatch!"); - IM_ASSERT(SizeOfStyleVarStack >= g.StyleVarStack.Size && "PushStyleVar/PopStyleVar Mismatch!"); - IM_ASSERT(SizeOfFontStack >= g.FontStack.Size && "PushFont/PopFont Mismatch!"); - IM_ASSERT(SizeOfFocusScopeStack == g.FocusScopeStack.Size && "PushFocusScope/PopFocusScope Mismatch!"); +void ImGui::EndErrorTooltip() +{ + End(); } //----------------------------------------------------------------------------- @@ -10134,7 +11408,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu g.LastItemData.ID = id; g.LastItemData.Rect = bb; g.LastItemData.NavRect = nav_bb_arg ? *nav_bb_arg : bb; - g.LastItemData.InFlags = g.CurrentItemFlags | g.NextItemData.ItemFlags | extra_flags; + g.LastItemData.ItemFlags = g.CurrentItemFlags | g.NextItemData.ItemFlags | extra_flags; g.LastItemData.StatusFlags = ImGuiItemStatusFlags_None; // Note: we don't copy 'g.NextItemData.SelectionUserData' to an hypothetical g.LastItemData.SelectionUserData: since the former is not cleared. @@ -10152,22 +11426,22 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu // to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick). // We intentionally don't check if g.NavWindow != NULL because g.NavAnyRequest should only be set when it is non null. // If we crash on a NULL g.NavWindow we need to fix the bug elsewhere. - if (!(g.LastItemData.InFlags & ImGuiItemFlags_NoNav)) + if (!(g.LastItemData.ItemFlags & ImGuiItemFlags_NoNav)) { // FIMXE-NAV: investigate changing the window tests into a simple 'if (g.NavFocusScopeId == g.CurrentFocusScopeId)' test. window->DC.NavLayersActiveMaskNext |= (1 << window->DC.NavLayerCurrent); if (g.NavId == id || g.NavAnyRequest) if (g.NavWindow->RootWindowForNav == window->RootWindowForNav) - if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened)) + if (window == g.NavWindow || ((window->ChildFlags | g.NavWindow->ChildFlags) & ImGuiChildFlags_NavFlattened)) NavProcessItem(); } - if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasShortcut) + if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasShortcut) ItemHandleShortcut(id); } // Lightweight clear of SetNextItemXXX data. - g.NextItemData.Flags = ImGuiNextItemDataFlags_None; + g.NextItemData.HasFlags = ImGuiNextItemDataFlags_None; g.NextItemData.ItemFlags = ImGuiItemFlags_None; #ifdef IMGUI_ENABLE_TEST_ENGINE @@ -10197,7 +11471,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu IM_ASSERT(id != window->ID && "Cannot have an empty ID at the root of a window. If you need an empty label, use ## and read the FAQ about how the ID Stack works!"); } //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] - //if ((g.LastItemData.InFlags & ImGuiItemFlags_NoNav) == 0) + //if ((g.LastItemData.ItemFlags & ImGuiItemFlags_NoNav) == 0) // window->DrawList->AddRect(g.LastItemData.NavRect.Min, g.LastItemData.NavRect.Max, IM_COL32(255,255,0,255)); // [DEBUG] #endif @@ -10233,9 +11507,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE // - GetFrameHeight() // - GetFrameHeightWithSpacing() // - GetContentRegionMax() -// - GetContentRegionMaxAbs() [Internal] // - GetContentRegionAvail(), -// - GetWindowContentRegionMin(), GetWindowContentRegionMax() // - BeginGroup() // - EndGroup() // Also see in imgui_widgets: tab bars, and in imgui_tables: tables, columns. @@ -10398,7 +11670,7 @@ void ImGui::Unindent(float indent_w) void ImGui::SetNextItemWidth(float item_width) { ImGuiContext& g = *GImGui; - g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasWidth; + g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasWidth; g.NextItemData.Width = item_width; } @@ -10409,7 +11681,7 @@ void ImGui::PushItemWidth(float item_width) ImGuiWindow* window = g.CurrentWindow; window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); // Backup current width window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width); - g.NextItemData.Flags &= ~ImGuiNextItemDataFlags_HasWidth; + g.NextItemData.HasFlags &= ~ImGuiNextItemDataFlags_HasWidth; } void ImGui::PushMultiItemsWidths(int components, float w_full) @@ -10428,12 +11700,18 @@ void ImGui::PushMultiItemsWidths(int components, float w_full) prev_split = next_split; } window->DC.ItemWidth = ImMax(prev_split, 1.0f); - g.NextItemData.Flags &= ~ImGuiNextItemDataFlags_HasWidth; + g.NextItemData.HasFlags &= ~ImGuiNextItemDataFlags_HasWidth; } void ImGui::PopItemWidth() { - ImGuiWindow* window = GetCurrentWindow(); + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->DC.ItemWidthStack.Size <= 0) + { + IM_ASSERT_USER_ERROR(0, "Calling PopItemWidth() too many times!"); + return; + } window->DC.ItemWidth = window->DC.ItemWidthStack.back(); window->DC.ItemWidthStack.pop_back(); } @@ -10445,14 +11723,14 @@ float ImGui::CalcItemWidth() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; float w; - if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasWidth) + if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasWidth) w = g.NextItemData.Width; else w = window->DC.ItemWidth; if (w < 0.0f) { - float region_max_x = GetContentRegionMaxAbs().x; - w = ImMax(1.0f, region_max_x - window->DC.CursorPos.x + w); + float region_avail_x = GetContentRegionAvail().x; + w = ImMax(1.0f, region_avail_x + w); } w = IM_TRUNC(w); return w; @@ -10464,22 +11742,19 @@ float ImGui::CalcItemWidth() // The 4.0f here may be changed to match CalcItemWidth() and/or BeginChild() (right now we have a mismatch which is harmless but undesirable) ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_w, float default_h) { - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - ImVec2 region_max; + ImVec2 avail; if (size.x < 0.0f || size.y < 0.0f) - region_max = GetContentRegionMaxAbs(); + avail = GetContentRegionAvail(); if (size.x == 0.0f) size.x = default_w; else if (size.x < 0.0f) - size.x = ImMax(4.0f, region_max.x - window->DC.CursorPos.x + size.x); + size.x = ImMax(4.0f, avail.x + size.x); // <-- size.x is negative here so we are subtracting if (size.y == 0.0f) size.y = default_h; else if (size.y < 0.0f) - size.y = ImMax(4.0f, region_max.y - window->DC.CursorPos.y + size.y); + size.y = ImMax(4.0f, avail.y + size.y); // <-- size.y is negative here so we are subtracting return size; } @@ -10508,33 +11783,23 @@ float ImGui::GetFrameHeightWithSpacing() return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y; } -// FIXME: All the Contents Region function are messy or misleading. WE WILL AIM TO OBSOLETE ALL OF THEM WITH A NEW "WORK RECT" API. Thanks for your patience! - -// FIXME: This is in window space (not screen space!). -ImVec2 ImGui::GetContentRegionMax() +ImVec2 ImGui::GetContentRegionAvail() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImVec2 mx = (window->DC.CurrentColumns || g.CurrentTable) ? window->WorkRect.Max : window->ContentRegionRect.Max; - return mx - window->Pos; + return mx - window->DC.CursorPos; } -// [Internal] Absolute coordinate. Saner. This is not exposed until we finishing refactoring work rect features. -ImVec2 ImGui::GetContentRegionMaxAbs() -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - ImVec2 mx = (window->DC.CurrentColumns || g.CurrentTable) ? window->WorkRect.Max : window->ContentRegionRect.Max; - return mx; -} +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -ImVec2 ImGui::GetContentRegionAvail() +// You should never need those functions. Always use GetCursorScreenPos() and GetContentRegionAvail()! +// They are bizarre local-coordinates which don't play well with scrolling. +ImVec2 ImGui::GetContentRegionMax() { - ImGuiWindow* window = GImGui->CurrentWindow; - return GetContentRegionMaxAbs() - window->DC.CursorPos; + return GetContentRegionAvail() + GetCursorScreenPos() - GetWindowPos(); } -// In window space (not screen space!) ImVec2 ImGui::GetWindowContentRegionMin() { ImGuiWindow* window = GImGui->CurrentWindow; @@ -10546,6 +11811,7 @@ ImVec2 ImGui::GetWindowContentRegionMax() ImGuiWindow* window = GImGui->CurrentWindow; return window->ContentRegionRect.Max - window->Pos; } +#endif // Lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) // Groups are currently a mishmash of functionalities which should perhaps be clarified and separated. @@ -10591,11 +11857,11 @@ void ImGui::EndGroup() if (window->DC.IsSetPos) ErrorCheckUsingSetCursorPosToExtendParentBoundaries(); - ImRect group_bb(group_data.BackupCursorPos, ImMax(window->DC.CursorMaxPos, group_data.BackupCursorPos)); - + // Include LastItemData.Rect.Max as a workaround for e.g. EndTable() undershooting with CursorMaxPos report. (#7543) + ImRect group_bb(group_data.BackupCursorPos, ImMax(ImMax(window->DC.CursorMaxPos, g.LastItemData.Rect.Max), group_data.BackupCursorPos)); window->DC.CursorPos = group_data.BackupCursorPos; window->DC.CursorPosPrevLine = group_data.BackupCursorPosPrevLine; - window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos); + window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, group_bb.Max); window->DC.Indent = group_data.BackupIndent; window->DC.GroupOffset = group_data.BackupGroupOffset; window->DC.CurrLineSize = group_data.BackupCurrLineSize; @@ -10610,7 +11876,7 @@ void ImGui::EndGroup() return; } - window->DC.CurrLineTextBaseOffset = ImMax(window->DC.PrevLineTextBaseOffset, group_data.BackupCurrLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now. + window->DC.CurrLineTextBaseOffset = ImMax(window->DC.PrevLineTextBaseOffset, group_data.BackupCurrLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now. ItemSize(group_bb.GetSize()); ItemAdd(group_bb, 0, NULL, ImGuiItemFlags_NoTabStop); @@ -10907,7 +12173,8 @@ bool ImGui::BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags ext { ImGuiContext& g = *GImGui; - if (g.DragDropWithinSource || g.DragDropWithinTarget) + const bool is_dragdrop_tooltip = g.DragDropWithinSource || g.DragDropWithinTarget; + if (is_dragdrop_tooltip) { // Drag and Drop tooltips are positioning differently than other tooltips: // - offset visibility to increase visibility around mouse. @@ -10915,24 +12182,31 @@ bool ImGui::BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags ext // We call SetNextWindowPos() to enforce position and disable clamping. // See FindBestWindowPosForPopup() for positionning logic of other tooltips (not drag and drop ones). //ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding; - ImVec2 tooltip_pos = g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET * g.Style.MouseCursorScale; - SetNextWindowPos(tooltip_pos); + const bool is_touchscreen = (g.IO.MouseSource == ImGuiMouseSource_TouchScreen); + if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) == 0) + { + ImVec2 tooltip_pos = is_touchscreen ? (g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET_TOUCH * g.Style.MouseCursorScale) : (g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET_MOUSE * g.Style.MouseCursorScale); + ImVec2 tooltip_pivot = is_touchscreen ? TOOLTIP_DEFAULT_PIVOT_TOUCH : ImVec2(0.0f, 0.0f); + SetNextWindowPos(tooltip_pos, ImGuiCond_None, tooltip_pivot); + } + SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60f); //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with checkboard has issue with transparent colors :( tooltip_flags |= ImGuiTooltipFlags_OverridePrevious; } - char window_name[16]; - ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount); - if (tooltip_flags & ImGuiTooltipFlags_OverridePrevious) - if (ImGuiWindow* window = FindWindowByName(window_name)) - if (window->Active) - { - // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a new one. - SetWindowHiddenAndSkipItemsForCurrentFrame(window); - ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount); - } - ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize; + const char* window_name_template = is_dragdrop_tooltip ? "##Tooltip_DragDrop_%02d" : "##Tooltip_%02d"; + char window_name[32]; + ImFormatString(window_name, IM_ARRAYSIZE(window_name), window_name_template, g.TooltipOverrideCount); + if ((tooltip_flags & ImGuiTooltipFlags_OverridePrevious) && g.TooltipPreviousWindow != NULL && g.TooltipPreviousWindow->Active) + { + // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a new one. + //IMGUI_DEBUG_LOG("[tooltip] '%s' already active, using +1 for this frame\n", window_name); + SetWindowHiddenAndSkipItemsForCurrentFrame(g.TooltipPreviousWindow); + ImFormatString(window_name, IM_ARRAYSIZE(window_name), window_name_template, ++g.TooltipOverrideCount); + } + + ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking; Begin(window_name, NULL, flags | extra_window_flags); // 2023-03-09: Added bool return value to the API, but currently always returning true. // If this ever returns false we need to update BeginDragDropSource() accordingly. @@ -11005,8 +12279,8 @@ bool ImGui::IsPopupOpen(ImGuiID id, ImGuiPopupFlags popup_flags) if (popup_flags & ImGuiPopupFlags_AnyPopupLevel) { // Return true if the popup is open anywhere in the popup stack - for (int n = 0; n < g.OpenPopupStack.Size; n++) - if (g.OpenPopupStack[n].PopupId == id) + for (ImGuiPopupData& popup_data : g.OpenPopupStack) + if (popup_data.PopupId == id) return true; return false; } @@ -11145,12 +12419,13 @@ void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to // Window -> Popup1 -> Window2(Ref) // - Clicking/focusing Popup1 will close Popup2 and Popup3: // Window -> Popup1(Ref) -> Popup2 -> Popup3 - // - Each popups may contain child windows, which is why we compare ->RootWindow! + // - Each popups may contain child windows, which is why we compare ->RootWindowDockTree! // Window -> Popup1 -> Popup1_Child -> Popup2 -> Popup2_Child // We step through every popup from bottom to top to validate their position relative to reference window. bool ref_window_is_descendent_of_popup = false; for (int n = popup_count_to_keep; n < g.OpenPopupStack.Size; n++) if (ImGuiWindow* popup_window = g.OpenPopupStack[n].Window) + //if (popup_window->RootWindowDockTree == ref_window->RootWindowDockTree) // FIXME-MERGE if (IsWindowWithinBeginStackOf(ref_window, popup_window)) { ref_window_is_descendent_of_popup = true; @@ -11187,6 +12462,9 @@ void ImGui::ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_ ImGuiContext& g = *GImGui; IMGUI_DEBUG_LOG_POPUP("[popup] ClosePopupToLevel(%d), restore_under=%d\n", remaining, restore_focus_to_window_under_popup); IM_ASSERT(remaining >= 0 && remaining < g.OpenPopupStack.Size); + if (g.DebugLogFlags & ImGuiDebugLogFlags_EventPopup) + for (int n = remaining; n < g.OpenPopupStack.Size; n++) + IMGUI_DEBUG_LOG_POPUP("[popup] - Closing PopupID 0x%08X Window \"%s\"\n", g.OpenPopupStack[n].PopupId, g.OpenPopupStack[n].Window ? g.OpenPopupStack[n].Window->Name : NULL); // Trim open popup stack ImGuiPopupData prev_popup = g.OpenPopupStack[remaining]; @@ -11251,7 +12529,7 @@ bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags) else ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame - bool is_open = Begin(name, NULL, extra_window_flags | ImGuiWindowFlags_Popup); + bool is_open = Begin(name, NULL, extra_window_flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoDocking); if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) EndPopup(); @@ -11295,11 +12573,11 @@ bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags fla // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window. if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) == 0) { - const ImGuiViewport* viewport = GetMainViewport(); + const ImGuiViewport* viewport = window->WasActive ? window->Viewport : GetMainViewport(); // FIXME-VIEWPORT: What may be our reference viewport? SetNextWindowPos(viewport->GetCenter(), ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f)); } - flags |= ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse; + flags |= ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking; const bool is_open = Begin(name, p_open, flags); if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display) { @@ -11486,8 +12764,19 @@ ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& s ImRect ImGui::GetPopupAllowedExtentRect(ImGuiWindow* window) { ImGuiContext& g = *GImGui; - IM_UNUSED(window); - ImRect r_screen = ((ImGuiViewportP*)(void*)GetMainViewport())->GetMainRect(); + ImRect r_screen; + if (window->ViewportAllowPlatformMonitorExtend >= 0) + { + // Extent with be in the frame of reference of the given viewport (so Min is likely to be negative here) + const ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[window->ViewportAllowPlatformMonitorExtend]; + r_screen.Min = monitor.WorkPos; + r_screen.Max = monitor.WorkPos + monitor.WorkSize; + } + else + { + // Use the full viewport area (not work area) for popups + r_screen = window->Viewport->GetMainRect(); + } ImVec2 padding = g.Style.DisplaySafeAreaPadding; r_screen.Expand(ImVec2((r_screen.GetWidth() > padding.x * 2) ? -padding.x : 0.0f, (r_screen.GetHeight() > padding.y * 2) ? -padding.y : 0.0f)); return r_screen; @@ -11502,8 +12791,7 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) { // Child menus typically request _any_ position within the parent menu item, and then we move the new menu outside the parent bounds. // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu. - IM_ASSERT(g.CurrentWindow == window); - ImGuiWindow* parent_window = g.CurrentWindowStack[g.CurrentWindowStack.Size - 2].Window; + ImGuiWindow* parent_window = window->ParentWindow; float horizontal_overlap = g.Style.ItemInnerSpacing.x; // We want some overlap to convey the relative depth of each menu (currently the amount of overlap is hard-coded to style.ItemSpacing.x). ImRect r_avoid; if (parent_window->DC.MenuBarAppending) @@ -11519,18 +12807,30 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) if (window->Flags & ImGuiWindowFlags_Tooltip) { // Position tooltip (always follows mouse + clamp within outer boundaries) - // Note that drag and drop tooltips are NOT using this path: BeginTooltipEx() manually sets their position. - // In theory we could handle both cases in same location, but requires a bit of shuffling as drag and drop tooltips are calling SetWindowPos() leading to 'window_pos_set_by_api' being set in Begin() + // FIXME: + // - Too many paths. One problem is that FindBestWindowPosForPopupEx() doesn't allow passing a suggested position (so touch screen path doesn't use it by default). + // - Drag and drop tooltips are not using this path either: BeginTooltipEx() manually sets their position. + // - Require some tidying up. In theory we could handle both cases in same location, but requires a bit of shuffling + // as drag and drop tooltips are calling SetNextWindowPos() leading to 'window_pos_set_by_api' being set in Begin(). IM_ASSERT(g.CurrentWindow == window); const float scale = g.Style.MouseCursorScale; const ImVec2 ref_pos = NavCalcPreferredRefPos(); - const ImVec2 tooltip_pos = ref_pos + TOOLTIP_DEFAULT_OFFSET * scale; + + if (g.IO.MouseSource == ImGuiMouseSource_TouchScreen && NavCalcPreferredRefPosSource() == ImGuiInputSource_Mouse) + { + ImVec2 tooltip_pos = ref_pos + TOOLTIP_DEFAULT_OFFSET_TOUCH * scale - (TOOLTIP_DEFAULT_PIVOT_TOUCH * window->Size); + if (r_outer.Contains(ImRect(tooltip_pos, tooltip_pos + window->Size))) + return tooltip_pos; + } + + ImVec2 tooltip_pos = ref_pos + TOOLTIP_DEFAULT_OFFSET_MOUSE * scale; ImRect r_avoid; - if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos)) + if (g.NavCursorVisible && g.NavHighlightItemUnderNav && !g.IO.ConfigNavMoveSetMousePos) r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8); else r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * scale, ref_pos.y + 24 * scale); // FIXME: Hard-coded based on mouse cursor shape expectation. Exact dimension not very important. //GetForegroundDrawList()->AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255, 0, 255, 255)); + return FindBestWindowPosForPopupEx(tooltip_pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid, ImGuiPopupPositionPolicy_Tooltip); } IM_ASSERT(0); @@ -11545,6 +12845,23 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) // In our terminology those should be interchangeable, yet right now this is super confusing. // Those two functions are merely a legacy artifact, so at minimum naming should be clarified. +void ImGui::SetNavCursorVisible(bool visible) +{ + ImGuiContext& g = *GImGui; + if (g.IO.ConfigNavCursorVisibleAlways) + visible = true; + g.NavCursorVisible = visible; +} + +// (was called NavRestoreHighlightAfterMove() before 1.91.4) +void ImGui::SetNavCursorVisibleAfterMove() +{ + ImGuiContext& g = *GImGui; + if (g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = true; + g.NavHighlightItemUnderNav = g.NavMousePosDirty = true; +} + void ImGui::SetNavWindow(ImGuiWindow* window) { ImGuiContext& g = *GImGui; @@ -11606,9 +12923,9 @@ void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) window->NavRectRel[nav_layer] = WindowRectAbsToRel(window, g.LastItemData.NavRect); if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) - g.NavDisableMouseHover = true; - else - g.NavDisableHighlight = true; + g.NavHighlightItemUnderNav = true; + else if (g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = false; // Clear preferred scoring position (NavMoveRequestApplyResult() will tend to restore it) NavClearPreferredPosForAxis(ImGuiAxis_X); @@ -11631,7 +12948,7 @@ static float inline NavScoreItemDistInterval(float cand_min, float cand_max, flo return 0.0f; } -// Scoring function for gamepad/keyboard directional navigation. Based on https://gist.github.com/rygorous/6981057 +// Scoring function for keyboard/gamepad directional navigation. Based on https://gist.github.com/rygorous/6981057 static bool ImGui::NavScoreItem(ImGuiNavItemData* result) { ImGuiContext& g = *GImGui; @@ -11647,7 +12964,7 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result) // When entering through a NavFlattened border, we consider child window items as fully clipped for scoring if (window->ParentWindow == g.NavWindow) { - IM_ASSERT((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened); + IM_ASSERT((window->ChildFlags | g.NavWindow->ChildFlags) & ImGuiChildFlags_NavFlattened); if (!window->ClipRect.Overlaps(cand)) return false; cand.ClipWithFull(window->ClipRect); // This allows the scored item to not overlap other candidates in the parent window @@ -11672,6 +12989,9 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result) if (dbx != 0.0f || dby != 0.0f) { // For non-overlapping boxes, use distance between boxes + // FIXME-NAV: Quadrant may be incorrect because of (1) dbx bias and (2) curr.Max.y bias applied by NavBiasScoringRect() where typically curr.Max.y==curr.Min.y + // One typical case where this happens, with style.WindowMenuButtonPosition == ImGuiDir_Right, pressing Left to navigate from Close to Collapse tends to fail. + // Also see #6344. Calling ImGetDirQuadrantFromDelta() with unbiased values may be good but side-effects are plenty. dax = dbx; day = dby; dist_axial = dist_box; @@ -11777,9 +13097,9 @@ static void ImGui::NavApplyItemToResult(ImGuiNavItemData* result) result->Window = window; result->ID = g.LastItemData.ID; result->FocusScopeId = g.CurrentFocusScopeId; - result->InFlags = g.LastItemData.InFlags; + result->ItemFlags = g.LastItemData.ItemFlags; result->RectRel = WindowRectAbsToRel(window, g.LastItemData.NavRect); - if (result->InFlags & ImGuiItemFlags_HasSelectionUserData) + if (result->ItemFlags & ImGuiItemFlags_HasSelectionUserData) { IM_ASSERT(g.NextItemData.SelectionUserData != ImGuiSelectionUserData_Invalid); result->SelectionUserData = g.NextItemData.SelectionUserData; // INTENTIONAL: At this point this field is not cleared in NextItemData. Avoid unnecessary copy to LastItemData. @@ -11802,7 +13122,7 @@ static void ImGui::NavProcessItem() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; const ImGuiID id = g.LastItemData.ID; - const ImGuiItemFlags item_flags = g.LastItemData.InFlags; + const ImGuiItemFlags item_flags = g.LastItemData.ItemFlags; // When inside a container that isn't scrollable with Left<>Right, clip NavRect accordingly (#2221) if (window->DC.NavIsScrollPushableX == false) @@ -11864,7 +13184,7 @@ static void ImGui::NavProcessItem() SetNavFocusScope(g.CurrentFocusScopeId); // Will set g.NavFocusScopeId AND store g.NavFocusScopePath g.NavFocusScopeId = g.CurrentFocusScopeId; g.NavIdIsAlive = true; - if (g.LastItemData.InFlags & ImGuiItemFlags_HasSelectionUserData) + if (g.LastItemData.ItemFlags & ImGuiItemFlags_HasSelectionUserData) { IM_ASSERT(g.NextItemData.SelectionUserData != ImGuiSelectionUserData_Invalid); g.NavLastValidSelectionUserData = g.NextItemData.SelectionUserData; // INTENTIONAL: At this point this field is not cleared in NextItemData. Avoid unnecessary copy to LastItemData. @@ -11950,6 +13270,7 @@ void ImGui::NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavM { ImGuiContext& g = *GImGui; IM_ASSERT(g.NavWindow != NULL); + //IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequestSubmit: dir %c, window \"%s\"\n", "-WENS"[move_dir + 1], g.NavWindow->Name); if (move_flags & ImGuiNavMoveFlags_IsTabbing) move_flags |= ImGuiNavMoveFlags_AllowCurrentNavId; @@ -11979,12 +13300,12 @@ void ImGui::NavMoveRequestResolveWithLastItem(ImGuiNavItemData* result) } // Called by TreePop() to implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere -void ImGui::NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiNavTreeNodeData* tree_node_data) +void ImGui::NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiTreeNodeStackData* tree_node_data) { ImGuiContext& g = *GImGui; g.NavMoveScoringItems = false; g.LastItemData.ID = tree_node_data->ID; - g.LastItemData.InFlags = tree_node_data->InFlags & ~ImGuiItemFlags_HasSelectionUserData; // Losing SelectionUserData, recovered next-frame (cheaper). + g.LastItemData.ItemFlags = tree_node_data->ItemFlags & ~ImGuiItemFlags_HasSelectionUserData; // Losing SelectionUserData, recovered next-frame (cheaper). g.LastItemData.NavRect = tree_node_data->NavRect; NavApplyItemToResult(result); // Result this instead of implementing a NavApplyPastTreeNodeToResult() NavClearPreferredPosForAxis(ImGuiAxis_Y); @@ -12041,6 +13362,9 @@ static ImGuiWindow* ImGui::NavRestoreLastChildNavWindow(ImGuiWindow* window) { if (window->NavLastChildNavWindow && window->NavLastChildNavWindow->WasActive) return window->NavLastChildNavWindow; + if (window->DockNodeAsHost && window->DockNodeAsHost->TabBar) + if (ImGuiTabItem* tab = TabBarFindMostRecentlySelectedTabForActiveWindow(window->DockNodeAsHost->TabBar)) + return tab->Window; return window; } @@ -12067,13 +13391,6 @@ void ImGui::NavRestoreLayer(ImGuiNavLayer layer) } } -void ImGui::NavRestoreHighlightAfterMove() -{ - ImGuiContext& g = *GImGui; - g.NavDisableHighlight = false; - g.NavDisableMouseHover = g.NavMousePosDirty = true; -} - static inline void ImGui::NavUpdateAnyRequestFlag() { ImGuiContext& g = *GImGui; @@ -12085,6 +13402,7 @@ static inline void ImGui::NavUpdateAnyRequestFlag() // This needs to be called before we submit any widget (aka in or before Begin) void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) { + // FIXME: ChildWindow test here is wrong for docking ImGuiContext& g = *GImGui; IM_ASSERT(window == g.NavWindow); @@ -12114,14 +13432,29 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) } } +static ImGuiInputSource ImGui::NavCalcPreferredRefPosSource() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.NavWindow; + const bool activated_shortcut = g.ActiveId != 0 && g.ActiveIdFromShortcut && g.ActiveId == g.LastItemData.ID; + + // Testing for !activated_shortcut here could in theory be removed if we decided that activating a remote shortcut altered one of the g.NavDisableXXX flag. + if ((!g.NavCursorVisible || !g.NavHighlightItemUnderNav || !window) && !activated_shortcut) + return ImGuiInputSource_Mouse; + else + return ImGuiInputSource_Keyboard; // or Nav in general +} + static ImVec2 ImGui::NavCalcPreferredRefPos() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.NavWindow; + ImGuiInputSource source = NavCalcPreferredRefPosSource(); + const bool activated_shortcut = g.ActiveId != 0 && g.ActiveIdFromShortcut && g.ActiveId == g.LastItemData.ID; // Testing for !activated_shortcut here could in theory be removed if we decided that activating a remote shortcut altered one of the g.NavDisableXXX flag. - if ((g.NavDisableHighlight || !g.NavDisableMouseHover || !window) && !activated_shortcut) + if (source == ImGuiInputSource_Mouse) { // Mouse (we need a fallback in case the mouse becomes invalid after being used) // The +1.0f offset when stored by OpenPopupEx() allows reopening this or another popup (same or another mouse button) while not moving the mouse, it is pretty standard. @@ -12145,7 +13478,7 @@ static ImVec2 ImGui::NavCalcPreferredRefPos() ref_rect.Translate(window->Scroll - next_scroll); } ImVec2 pos = ImVec2(ref_rect.Min.x + ImMin(g.Style.FramePadding.x * 4, ref_rect.GetWidth()), ref_rect.Max.y - ImMin(g.Style.FramePadding.y, ref_rect.GetHeight())); - ImGuiViewport* viewport = GetMainViewport(); + ImGuiViewport* viewport = window->Viewport; return ImTrunc(ImClamp(pos, viewport->Pos, viewport->Pos + viewport->Size)); // ImTrunc() is important because non-integer mouse position application in backend might be lossy and result in undesirable non-zero delta. } } @@ -12198,6 +13531,7 @@ static void ImGui::NavUpdate() // Process navigation init request (select first/default focus) g.NavJustMovedToId = 0; + g.NavJustMovedToFocusScopeId = g.NavJustMovedFromFocusScopeId = 0; if (g.NavInitResult.ID != 0) NavInitRequestApplyResult(); g.NavInitRequest = false; @@ -12209,11 +13543,14 @@ static void ImGui::NavUpdate() NavMoveRequestApplyResult(); g.NavTabbingCounter = 0; g.NavMoveSubmitted = g.NavMoveScoringItems = false; + if (g.NavCursorHideFrames > 0) + if (--g.NavCursorHideFrames == 0) + g.NavCursorVisible = true; // Schedule mouse position update (will be done at the bottom of this function, after 1) processing all move requests and 2) updating scrolling) bool set_mouse_pos = false; if (g.NavMousePosDirty && g.NavIdIsAlive) - if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow) + if (g.NavCursorVisible && g.NavHighlightItemUnderNav && g.NavWindow) set_mouse_pos = true; g.NavMousePosDirty = false; IM_ASSERT(g.NavLayer == ImGuiNavLayer_Main || g.NavLayer == ImGuiNavLayer_Menu); @@ -12229,7 +13566,7 @@ static void ImGui::NavUpdate() // Set output flags for user application io.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs); - io.NavVisible = (io.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL); + io.NavVisible = (io.NavActive && g.NavId != 0 && g.NavCursorVisible) || (g.NavWindowingTarget != NULL); // Process NavCancel input (to close a popup, get back to parent, clear focus) NavUpdateCancelRequest(); @@ -12237,7 +13574,7 @@ static void ImGui::NavUpdate() // Process manual activation request g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = 0; g.NavActivateFlags = ImGuiActivateFlags_None; - if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + if (g.NavId != 0 && g.NavCursorVisible && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) { const bool activate_down = (nav_keyboard_active && IsKeyDown(ImGuiKey_Space, ImGuiKeyOwner_NoOwner)) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadActivate, ImGuiKeyOwner_NoOwner)); const bool activate_pressed = activate_down && ((nav_keyboard_active && IsKeyPressed(ImGuiKey_Space, 0, ImGuiKeyOwner_NoOwner)) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, 0, ImGuiKeyOwner_NoOwner))); @@ -12262,7 +13599,9 @@ static void ImGui::NavUpdate() } } if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) - g.NavDisableHighlight = true; + g.NavCursorVisible = false; + else if (g.IO.ConfigNavCursorVisibleAlways && g.NavCursorHideFrames == 0) + g.NavCursorVisible = true; if (g.NavActivateId != 0) IM_ASSERT(g.NavActivateDownId == g.NavActivateId); @@ -12319,13 +13658,13 @@ static void ImGui::NavUpdate() // Always prioritize mouse highlight if navigation is disabled if (!nav_keyboard_active && !nav_gamepad_active) { - g.NavDisableHighlight = true; - g.NavDisableMouseHover = set_mouse_pos = false; + g.NavCursorVisible = false; + g.NavHighlightItemUnderNav = set_mouse_pos = false; } // Update mouse position if requested // (This will take into account the possibility that a Scroll was queued in the window to offset our absolute mouse position before scroll has been applied) - if (set_mouse_pos && (io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) && (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos)) + if (set_mouse_pos && io.ConfigNavMoveSetMousePos && (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos)) TeleportMousePos(NavCalcPreferredRefPos()); // [DEBUG] @@ -12350,9 +13689,12 @@ void ImGui::NavInitRequestApplyResult() ImGuiNavItemData* result = &g.NavInitResult; if (g.NavId != result->ID) { + g.NavJustMovedFromFocusScopeId = g.NavFocusScopeId; g.NavJustMovedToId = result->ID; g.NavJustMovedToFocusScopeId = result->FocusScopeId; g.NavJustMovedToKeyMods = 0; + g.NavJustMovedToIsTabbing = false; + g.NavJustMovedToHasSelectionData = (result->ItemFlags & ImGuiItemFlags_HasSelectionUserData) != 0; } // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called) @@ -12363,7 +13705,7 @@ void ImGui::NavInitRequestApplyResult() if (result->SelectionUserData != ImGuiSelectionUserData_Invalid) g.NavLastValidSelectionUserData = result->SelectionUserData; if (g.NavInitRequestFromMove) - NavRestoreHighlightAfterMove(); + SetNavCursorVisibleAfterMove(); } // Bias scoring rect ahead of scoring + update preferred pos (if missing) using source position @@ -12460,7 +13802,8 @@ void ImGui::NavUpdateCreateMoveRequest() IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from move, window \"%s\", layer=%d\n", window ? window->Name : "", g.NavLayer); g.NavInitRequest = g.NavInitRequestFromMove = true; g.NavInitResult.ID = 0; - g.NavDisableHighlight = false; + if (g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = true; } // When using gamepad, we project the reference nav bounding box into window visible area. @@ -12524,7 +13867,7 @@ void ImGui::NavUpdateCreateTabbingRequest() // See NavProcessItemForTabbingRequest() for a description of the various forward/backward tabbing cases with and without wrapping. const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; if (nav_keyboard_active) - g.NavTabbingDir = g.IO.KeyShift ? -1 : (g.NavDisableHighlight == true && g.ActiveId == 0) ? 0 : +1; + g.NavTabbingDir = g.IO.KeyShift ? -1 : (g.NavCursorVisible == false && g.ActiveId == 0) ? 0 : +1; else g.NavTabbingDir = g.IO.KeyShift ? -1 : (g.ActiveId == 0) ? 0 : +1; ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_Activate; @@ -12556,9 +13899,9 @@ void ImGui::NavMoveRequestApplyResult() if (result == NULL) { if (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) - g.NavMoveFlags |= ImGuiNavMoveFlags_NoSetNavHighlight; - if (g.NavId != 0 && (g.NavMoveFlags & ImGuiNavMoveFlags_NoSetNavHighlight) == 0) - NavRestoreHighlightAfterMove(); + g.NavMoveFlags |= ImGuiNavMoveFlags_NoSetNavCursorVisible; + if (g.NavId != 0 && (g.NavMoveFlags & ImGuiNavMoveFlags_NoSetNavCursorVisible) == 0) + SetNavCursorVisibleAfterMove(); NavClearPreferredPosForAxis(axis); // On a failed move, clear preferred pos for this axis. IMGUI_DEBUG_LOG_NAV("[nav] NavMoveSubmitted but not led to a result!\n"); return; @@ -12606,9 +13949,13 @@ void ImGui::NavMoveRequestApplyResult() // PageUp/PageDown however sets always set NavJustMovedTo (vs Home/End which doesn't) mimicking Windows behavior. if ((g.NavId != result->ID || (g.NavMoveFlags & ImGuiNavMoveFlags_IsPageMove)) && (g.NavMoveFlags & ImGuiNavMoveFlags_NoSelect) == 0) { + g.NavJustMovedFromFocusScopeId = g.NavFocusScopeId; g.NavJustMovedToId = result->ID; g.NavJustMovedToFocusScopeId = result->FocusScopeId; g.NavJustMovedToKeyMods = g.NavMoveKeyMods; + g.NavJustMovedToIsTabbing = (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) != 0; + g.NavJustMovedToHasSelectionData = (result->ItemFlags & ImGuiItemFlags_HasSelectionUserData) != 0; + //IMGUI_DEBUG_LOG_NAV("[nav] NavJustMovedFromFocusScopeId = 0x%08X, NavJustMovedToFocusScopeId = 0x%08X\n", g.NavJustMovedFromFocusScopeId, g.NavJustMovedToFocusScopeId); } // Apply new NavID/Focus @@ -12627,7 +13974,7 @@ void ImGui::NavMoveRequestApplyResult() } // Tabbing: Activates Inputable, otherwise only Focus - if ((g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) && (result->InFlags & ImGuiItemFlags_Inputable) == 0) + if ((g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) && (result->ItemFlags & ImGuiItemFlags_Inputable) == 0) g.NavMoveFlags &= ~ImGuiNavMoveFlags_Activate; // Activate @@ -12639,12 +13986,12 @@ void ImGui::NavMoveRequestApplyResult() g.NavNextActivateFlags |= ImGuiActivateFlags_PreferInput | ImGuiActivateFlags_TryToPreserveState | ImGuiActivateFlags_FromTabbing; } - // Enable nav highlight - if ((g.NavMoveFlags & ImGuiNavMoveFlags_NoSetNavHighlight) == 0) - NavRestoreHighlightAfterMove(); + // Make nav cursor visible + if ((g.NavMoveFlags & ImGuiNavMoveFlags_NoSetNavCursorVisible) == 0) + SetNavCursorVisibleAfterMove(); } -// Process NavCancel input (to close a popup, get back to parent, clear focus) +// Process Escape/NavCancel input (to close a popup, get back to parent, clear focus) // FIXME: In order to support e.g. Escape to clear a selection we'll need: // - either to store the equivalent of ActiveIdUsingKeyInputMask for a FocusScope and test for it. // - either to move most/all of those tests to the epilogue/end functions of the scope they are dealing with (e.g. exit child window in EndChild()) or in EndFrame(), to allow an earlier intercept @@ -12665,7 +14012,7 @@ static void ImGui::NavUpdateCancelRequest() { // Leave the "menu" layer NavRestoreLayer(ImGuiNavLayer_Main); - NavRestoreHighlightAfterMove(); + SetNavCursorVisibleAfterMove(); } else if (g.NavWindow && g.NavWindow != g.NavWindow->RootWindow && !(g.NavWindow->RootWindowForNav->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->RootWindowForNav->ParentWindow) { @@ -12675,7 +14022,7 @@ static void ImGui::NavUpdateCancelRequest() IM_ASSERT(child_window->ChildId != 0); FocusWindow(parent_window); SetNavID(child_window->ChildId, ImGuiNavLayer_Main, 0, WindowRectAbsToRel(parent_window, child_window->Rect())); - NavRestoreHighlightAfterMove(); + SetNavCursorVisibleAfterMove(); } else if (g.OpenPopupStack.Size > 0 && g.OpenPopupStack.back().Window != NULL && !(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) { @@ -12685,9 +14032,16 @@ static void ImGui::NavUpdateCancelRequest() else { // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were - if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow))) - g.NavWindow->NavLastIds[0] = 0; - g.NavId = 0; + // FIXME-NAV: This should happen on window appearing. + if (g.IO.ConfigNavEscapeClearFocusItem || g.IO.ConfigNavEscapeClearFocusWindow) + if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup)))// || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow))) + g.NavWindow->NavLastIds[0] = 0; + + // Clear nav focus + if (g.IO.ConfigNavEscapeClearFocusItem || g.IO.ConfigNavEscapeClearFocusWindow) + g.NavId = 0; + if (g.IO.ConfigNavEscapeClearFocusWindow) + FocusWindow(NULL); } } @@ -12862,7 +14216,7 @@ static ImGuiWindow* FindWindowNavFocusable(int i_start, int i_stop, int dir) // return NULL; } -static void NavUpdateWindowingHighlightWindow(int focus_change_dir) +static void NavUpdateWindowingTarget(int focus_change_dir) { ImGuiContext& g = *GImGui; IM_ASSERT(g.NavWindowingTarget); @@ -12914,14 +14268,17 @@ static void ImGui::NavUpdateWindowing() const bool keyboard_prev_window = allow_windowing && g.ConfigNavWindowingKeyPrev && Shortcut(g.ConfigNavWindowingKeyPrev, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways, owner_id); const bool start_windowing_with_gamepad = allow_windowing && nav_gamepad_active && !g.NavWindowingTarget && IsKeyPressed(ImGuiKey_NavGamepadMenu, ImGuiInputFlags_None); const bool start_windowing_with_keyboard = allow_windowing && !g.NavWindowingTarget && (keyboard_next_window || keyboard_prev_window); // Note: enabled even without NavEnableKeyboard! + bool just_started_windowing_from_null_focus = false; if (start_windowing_with_gamepad || start_windowing_with_keyboard) if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavFocusable(g.WindowsFocusOrder.Size - 1, -INT_MAX, -1)) { - g.NavWindowingTarget = g.NavWindowingTargetAnim = window->RootWindow; + g.NavWindowingTarget = g.NavWindowingTargetAnim = window->RootWindow; // Current location g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f; g.NavWindowingAccumDeltaPos = g.NavWindowingAccumDeltaSize = ImVec2(0.0f, 0.0f); g.NavWindowingToggleLayer = start_windowing_with_gamepad ? true : false; // Gamepad starts toggling layer g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_Keyboard : ImGuiInputSource_Gamepad; + if (g.NavWindow == NULL) + just_started_windowing_from_null_focus = true; // Manually register ownership of our mods. Using a global route in the Shortcut() calls instead would probably be correct but may have more side-effects. if (keyboard_next_window || keyboard_prev_window) @@ -12937,9 +14294,9 @@ static void ImGui::NavUpdateWindowing() // Select window to focus const int focus_change_dir = (int)IsKeyPressed(ImGuiKey_GamepadL1) - (int)IsKeyPressed(ImGuiKey_GamepadR1); - if (focus_change_dir != 0) + if (focus_change_dir != 0 && !just_started_windowing_from_null_focus) { - NavUpdateWindowingHighlightWindow(focus_change_dir); + NavUpdateWindowingTarget(focus_change_dir); g.NavWindowingHighlightAlpha = 1.0f; } @@ -12962,17 +14319,19 @@ static void ImGui::NavUpdateWindowing() ImGuiKeyChord shared_mods = ((g.ConfigNavWindowingKeyNext ? g.ConfigNavWindowingKeyNext : ImGuiMod_Mask_) & (g.ConfigNavWindowingKeyPrev ? g.ConfigNavWindowingKeyPrev : ImGuiMod_Mask_)) & ImGuiMod_Mask_; IM_ASSERT(shared_mods != 0); // Next/Prev shortcut currently needs a shared modifier to "hold", otherwise Prev actions would keep cycling between two windows. g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f - if (keyboard_next_window || keyboard_prev_window) - NavUpdateWindowingHighlightWindow(keyboard_next_window ? -1 : +1); + if ((keyboard_next_window || keyboard_prev_window) && !just_started_windowing_from_null_focus) + NavUpdateWindowingTarget(keyboard_next_window ? -1 : +1); else if ((io.KeyMods & shared_mods) != shared_mods) apply_focus_window = g.NavWindowingTarget; } // Keyboard: Press and Release ALT to toggle menu layer const ImGuiKey windowing_toggle_keys[] = { ImGuiKey_LeftAlt, ImGuiKey_RightAlt }; + bool windowing_toggle_layer_start = false; for (ImGuiKey windowing_toggle_key : windowing_toggle_keys) if (nav_keyboard_active && IsKeyPressed(windowing_toggle_key, 0, ImGuiKeyOwner_NoOwner)) { + windowing_toggle_layer_start = true; g.NavWindowingToggleLayer = true; g.NavWindowingToggleKey = windowing_toggle_key; g.NavInputSource = ImGuiInputSource_Keyboard; @@ -12986,7 +14345,9 @@ static void ImGui::NavUpdateWindowing() // We cancel toggling nav layer if an owner has claimed the key. if (io.InputQueueCharacters.Size > 0 || io.KeyCtrl || io.KeyShift || io.KeySuper) g.NavWindowingToggleLayer = false; - if (TestKeyOwner(g.NavWindowingToggleKey, ImGuiKeyOwner_NoOwner) == false || TestKeyOwner(ImGuiMod_Alt, ImGuiKeyOwner_NoOwner) == false) + else if (windowing_toggle_layer_start == false && g.LastKeyboardKeyPressTime == g.Time) + g.NavWindowingToggleLayer = false; + else if (TestKeyOwner(g.NavWindowingToggleKey, ImGuiKeyOwner_NoOwner) == false || TestKeyOwner(ImGuiMod_Alt, ImGuiKeyOwner_NoOwner) == false) g.NavWindowingToggleLayer = false; // Apply layer toggle on Alt release @@ -13012,11 +14373,11 @@ static void ImGui::NavUpdateWindowing() const float NAV_MOVE_SPEED = 800.0f; const float move_step = NAV_MOVE_SPEED * io.DeltaTime * ImMin(io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); g.NavWindowingAccumDeltaPos += nav_move_dir * move_step; - g.NavDisableMouseHover = true; + g.NavHighlightItemUnderNav = true; ImVec2 accum_floored = ImTrunc(g.NavWindowingAccumDeltaPos); if (accum_floored.x != 0.0f || accum_floored.y != 0.0f) { - ImGuiWindow* moving_window = g.NavWindowingTarget->RootWindow; + ImGuiWindow* moving_window = g.NavWindowingTarget->RootWindowDockTree; SetWindowPos(moving_window, moving_window->Pos + accum_floored, ImGuiCond_Always); g.NavWindowingAccumDeltaPos -= accum_floored; } @@ -13026,8 +14387,11 @@ static void ImGui::NavUpdateWindowing() // Apply final focus if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow)) { + // FIXME: Many actions here could be part of a higher-level/reused function. Why aren't they in FocusWindow() + // Investigate for each of them: ClearActiveID(), NavRestoreHighlightAfterMove(), NavRestoreLastChildNavWindow(), ClosePopupsOverWindow(), NavInitWindow() + ImGuiViewport* previous_viewport = g.NavWindow ? g.NavWindow->Viewport : NULL; ClearActiveID(); - NavRestoreHighlightAfterMove(); + SetNavCursorVisibleAfterMove(); ClosePopupsOverWindow(apply_focus_window, false); FocusWindow(apply_focus_window, ImGuiFocusRequestFlags_RestoreFocusedChild); apply_focus_window = g.NavWindow; @@ -13043,6 +14407,10 @@ static void ImGui::NavUpdateWindowing() // won't be valid. if (apply_focus_window->DC.NavLayersActiveMaskNext == (1 << ImGuiNavLayer_Menu)) g.NavLayer = ImGuiNavLayer_Menu; + + // Request OS level focus + if (apply_focus_window->Viewport != previous_viewport && g.PlatformIO.Platform_SetWindowFocus) + g.PlatformIO.Platform_SetWindowFocus(apply_focus_window->Viewport); } if (apply_focus_window) g.NavWindowingTarget = NULL; @@ -13071,10 +14439,11 @@ static void ImGui::NavUpdateWindowing() if (new_nav_layer != g.NavLayer) { // Reinitialize navigation when entering menu bar with the Alt key (FIXME: could be a properly of the layer?) - if (new_nav_layer == ImGuiNavLayer_Menu) + const bool preserve_layer_1_nav_id = (new_nav_window->DockNodeAsHost != NULL); + if (new_nav_layer == ImGuiNavLayer_Menu && !preserve_layer_1_nav_id) g.NavWindow->NavLastIds[new_nav_layer] = 0; NavRestoreLayer(new_nav_layer); - NavRestoreHighlightAfterMove(); + SetNavCursorVisibleAfterMove(); } } } @@ -13086,6 +14455,8 @@ static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window) return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingPopup); if ((window->Flags & ImGuiWindowFlags_MenuBar) && strcmp(window->Name, "##MainMenuBar") == 0) return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingMainMenuBar); + if (window->DockNodeAsHost) + return "(Dock node)"; // Not normally shown to user. return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingUntitled); } @@ -13100,11 +14471,13 @@ void ImGui::NavUpdateWindowingOverlay() if (g.NavWindowingListWindow == NULL) g.NavWindowingListWindow = FindWindowByName("###NavWindowingList"); - const ImGuiViewport* viewport = GetMainViewport(); + const ImGuiViewport* viewport = /*g.NavWindow ? g.NavWindow->Viewport :*/ GetMainViewport(); SetNextWindowSizeConstraints(ImVec2(viewport->Size.x * 0.20f, viewport->Size.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX)); SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f); Begin("###NavWindowingList", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings); + if (g.ContextName[0] != 0) + SeparatorText(g.ContextName); for (int n = g.WindowsFocusOrder.Size - 1; n >= 0; n--) { ImGuiWindow* window = g.WindowsFocusOrder[n]; @@ -13211,7 +14584,7 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) // Rely on keeping other window->LastItemXXX fields intact. source_id = g.LastItemData.ID = window->GetIDFromRectangle(g.LastItemData.Rect); KeepAliveID(source_id); - bool is_hovered = ItemHoverable(g.LastItemData.Rect, source_id, g.LastItemData.InFlags); + bool is_hovered = ItemHoverable(g.LastItemData.Rect, source_id, g.LastItemData.ItemFlags); if (is_hovered && g.IO.MouseClicked[mouse_button]) { SetActiveID(source_id, window); @@ -13230,9 +14603,13 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) } else { + // When ImGuiDragDropFlags_SourceExtern is set: window = NULL; source_id = ImHashStr("#SourceExtern"); source_drag_active = true; + mouse_button = g.IO.MouseDown[0] ? 0 : -1; + KeepAliveID(source_id); + SetActiveID(source_id, NULL); } IM_ASSERT(g.DragDropWithinTarget == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget() @@ -13244,7 +14621,8 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) { IM_ASSERT(source_id != 0); ClearDragDrop(); - IMGUI_DEBUG_LOG_ACTIVEID("[dragdrop] BeginDragDropSource() DragDropActive = true, source_id = %08X\n", source_id); + IMGUI_DEBUG_LOG_ACTIVEID("[dragdrop] BeginDragDropSource() DragDropActive = true, source_id = 0x%08X%s\n", + source_id, (flags & ImGuiDragDropFlags_SourceExtern) ? " (EXTERN)" : ""); ImGuiPayload& payload = g.DragDropPayload; payload.SourceId = source_id; payload.SourceParentId = source_parent_id; @@ -13303,7 +14681,7 @@ bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_s IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long"); IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0)); IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once); - IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource() + IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource() if (cond == ImGuiCond_Always || payload.DataFrameCount == -1) { @@ -13344,7 +14722,7 @@ bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) ImGuiWindow* window = g.CurrentWindow; ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow; - if (hovered_window == NULL || window->RootWindow != hovered_window->RootWindow) + if (hovered_window == NULL || window->RootWindowDockTree != hovered_window->RootWindowDockTree) return false; IM_ASSERT(id != 0); if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId)) @@ -13354,7 +14732,7 @@ bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) IM_ASSERT(g.DragDropWithinTarget == false && g.DragDropWithinSource == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget() g.DragDropTargetRect = bb; - g.DragDropTargetClipRect = window->ClipRect; // May want to be overriden by user depending on use case? + g.DragDropTargetClipRect = window->ClipRect; // May want to be overridden by user depending on use case? g.DragDropTargetId = id; g.DragDropWithinTarget = true; return true; @@ -13374,7 +14752,7 @@ bool ImGui::BeginDragDropTarget() if (!(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect)) return false; ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow; - if (hovered_window == NULL || window->RootWindow != hovered_window->RootWindow || window->SkipItems) + if (hovered_window == NULL || window->RootWindowDockTree != hovered_window->RootWindowDockTree || window->SkipItems) return false; const ImRect& display_rect = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? g.LastItemData.DisplayRect : g.LastItemData.Rect; @@ -13430,11 +14808,15 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop RenderDragDropTargetRect(r, g.DragDropTargetClipRect); g.DragDropAcceptFrameCount = g.FrameCount; - payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting OS window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased() + if ((g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) && g.DragDropMouseButton == -1) + payload.Delivery = was_accepted_previously && (g.DragDropSourceFrameCount < g.FrameCount); + else + payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting OS window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased() if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery)) return NULL; - //IMGUI_DEBUG_LOG("AcceptDragDropPayload(): %08X: return payload\n", g.DragDropTargetId); + if (payload.Delivery) + IMGUI_DEBUG_LOG_ACTIVEID("[dragdrop] AcceptDragDropPayload(): 0x%08X: payload delivery\n", g.DragDropTargetId); return &payload; } @@ -13577,15 +14959,17 @@ void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* } // Start logging/capturing text output -void ImGui::LogBegin(ImGuiLogType type, int auto_open_depth) +void ImGui::LogBegin(ImGuiLogFlags flags, int auto_open_depth) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; IM_ASSERT(g.LogEnabled == false); - IM_ASSERT(g.LogFile == NULL); - IM_ASSERT(g.LogBuffer.empty()); + IM_ASSERT(g.LogFile == NULL && g.LogBuffer.empty()); + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiLogFlags_OutputMask_)); // Check that only 1 type flag is used + g.LogEnabled = g.ItemUnclipByLog = true; - g.LogType = type; + g.LogFlags = flags; + g.LogWindow = window; g.LogNextPrefix = g.LogNextSuffix = NULL; g.LogDepthRef = window->DC.TreeDepth; g.LogDepthToExpand = ((auto_open_depth >= 0) ? auto_open_depth : g.LogDepthToExpandDefault); @@ -13608,7 +14992,7 @@ void ImGui::LogToTTY(int auto_open_depth) return; IM_UNUSED(auto_open_depth); #ifndef IMGUI_DISABLE_TTY_FUNCTIONS - LogBegin(ImGuiLogType_TTY, auto_open_depth); + LogBegin(ImGuiLogFlags_OutputTTY, auto_open_depth); g.LogFile = stdout; #endif } @@ -13634,7 +15018,7 @@ void ImGui::LogToFile(int auto_open_depth, const char* filename) return; } - LogBegin(ImGuiLogType_File, auto_open_depth); + LogBegin(ImGuiLogFlags_OutputFile, auto_open_depth); g.LogFile = f; } @@ -13644,7 +15028,7 @@ void ImGui::LogToClipboard(int auto_open_depth) ImGuiContext& g = *GImGui; if (g.LogEnabled) return; - LogBegin(ImGuiLogType_Clipboard, auto_open_depth); + LogBegin(ImGuiLogFlags_OutputClipboard, auto_open_depth); } void ImGui::LogToBuffer(int auto_open_depth) @@ -13652,7 +15036,7 @@ void ImGui::LogToBuffer(int auto_open_depth) ImGuiContext& g = *GImGui; if (g.LogEnabled) return; - LogBegin(ImGuiLogType_Buffer, auto_open_depth); + LogBegin(ImGuiLogFlags_OutputBuffer, auto_open_depth); } void ImGui::LogFinish() @@ -13662,29 +15046,29 @@ void ImGui::LogFinish() return; LogText(IM_NEWLINE); - switch (g.LogType) + switch (g.LogFlags & ImGuiLogFlags_OutputMask_) { - case ImGuiLogType_TTY: + case ImGuiLogFlags_OutputTTY: #ifndef IMGUI_DISABLE_TTY_FUNCTIONS fflush(g.LogFile); #endif break; - case ImGuiLogType_File: + case ImGuiLogFlags_OutputFile: ImFileClose(g.LogFile); break; - case ImGuiLogType_Buffer: + case ImGuiLogFlags_OutputBuffer: break; - case ImGuiLogType_Clipboard: + case ImGuiLogFlags_OutputClipboard: if (!g.LogBuffer.empty()) SetClipboardText(g.LogBuffer.begin()); break; - case ImGuiLogType_None: + default: IM_ASSERT(0); break; } g.LogEnabled = g.ItemUnclipByLog = false; - g.LogType = ImGuiLogType_None; + g.LogFlags = ImGuiLogFlags_None; g.LogFile = NULL; g.LogBuffer.clear(); } @@ -13703,10 +15087,10 @@ void ImGui::LogButtons() #endif const bool log_to_file = Button("Log To File"); SameLine(); const bool log_to_clipboard = Button("Log To Clipboard"); SameLine(); - PushTabStop(false); + PushItemFlag(ImGuiItemFlags_NoTabStop, true); SetNextItemWidth(80.0f); SliderInt("Default Depth", &g.LogDepthToExpandDefault, 0, 9, NULL); - PopTabStop(); + PopItemFlag(); PopID(); // Start logging at the end of the function so that the buttons don't appear in the log @@ -13975,11 +15359,14 @@ ImGuiWindowSettings* ImGui::FindWindowSettingsByWindow(ImGuiWindow* window) void ImGui::ClearWindowSettings(const char* name) { //IMGUI_DEBUG_LOG("ClearWindowSettings('%s')\n", name); + ImGuiContext& g = *GImGui; ImGuiWindow* window = FindWindowByName(name); if (window != NULL) { window->Flags |= ImGuiWindowFlags_NoSavedSettings; InitOrLoadWindowSettings(window, NULL); + if (window->DockId != 0) + DockContextProcessUndockWindow(&g, window, true); } if (ImGuiWindowSettings* settings = window ? FindWindowSettingsByWindow(window) : FindWindowSettingsByID(ImHashStr(name))) settings->WantDelete = true; @@ -14011,10 +15398,16 @@ static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry; int x, y; int i; - if (sscanf(line, "Pos=%i,%i", &x, &y) == 2) { settings->Pos = ImVec2ih((short)x, (short)y); } - else if (sscanf(line, "Size=%i,%i", &x, &y) == 2) { settings->Size = ImVec2ih((short)x, (short)y); } - else if (sscanf(line, "Collapsed=%d", &i) == 1) { settings->Collapsed = (i != 0); } - else if (sscanf(line, "IsChild=%d", &i) == 1) { settings->IsChild = (i != 0); } + ImU32 u1; + if (sscanf(line, "Pos=%i,%i", &x, &y) == 2) { settings->Pos = ImVec2ih((short)x, (short)y); } + else if (sscanf(line, "Size=%i,%i", &x, &y) == 2) { settings->Size = ImVec2ih((short)x, (short)y); } + else if (sscanf(line, "ViewportId=0x%08X", &u1) == 1) { settings->ViewportId = u1; } + else if (sscanf(line, "ViewportPos=%i,%i", &x, &y) == 2){ settings->ViewportPos = ImVec2ih((short)x, (short)y); } + else if (sscanf(line, "Collapsed=%d", &i) == 1) { settings->Collapsed = (i != 0); } + else if (sscanf(line, "IsChild=%d", &i) == 1) { settings->IsChild = (i != 0); } + else if (sscanf(line, "DockId=0x%X,%d", &u1, &i) == 2) { settings->DockId = u1; settings->DockOrder = (short)i; } + else if (sscanf(line, "DockId=0x%X", &u1) == 1) { settings->DockId = u1; settings->DockOrder = -1; } + else if (sscanf(line, "ClassId=0x%X", &u1) == 1) { settings->ClassId = u1; } } // Apply to existing windows (if any) @@ -14047,10 +15440,16 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings); } IM_ASSERT(settings->ID == window->ID); - settings->Pos = ImVec2ih(window->Pos); + settings->Pos = ImVec2ih(window->Pos - window->ViewportPos); settings->Size = ImVec2ih(window->SizeFull); - settings->IsChild = (window->Flags & ImGuiWindowFlags_ChildWindow) != 0; + settings->ViewportId = window->ViewportId; + settings->ViewportPos = ImVec2ih(window->ViewportPos); + IM_ASSERT(window->DockNode == NULL || window->DockNode->ID == window->DockId); + settings->DockId = window->DockId; + settings->ClassId = window->WindowClass.ClassId; + settings->DockOrder = window->DockOrder; settings->Collapsed = window->Collapsed; + settings->IsChild = (window->RootWindow != window); // Cannot rely on ImGuiWindowFlags_ChildWindow here as docked windows have this set. settings->WantDelete = false; } @@ -14069,10 +15468,26 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl } else { - buf->appendf("Pos=%d,%d\n", settings->Pos.x, settings->Pos.y); - buf->appendf("Size=%d,%d\n", settings->Size.x, settings->Size.y); - if (settings->Collapsed) - buf->appendf("Collapsed=1\n"); + if (settings->ViewportId != 0 && settings->ViewportId != ImGui::IMGUI_VIEWPORT_DEFAULT_ID) + { + buf->appendf("ViewportPos=%d,%d\n", settings->ViewportPos.x, settings->ViewportPos.y); + buf->appendf("ViewportId=0x%08X\n", settings->ViewportId); + } + if (settings->Pos.x != 0 || settings->Pos.y != 0 || settings->ViewportId == ImGui::IMGUI_VIEWPORT_DEFAULT_ID) + buf->appendf("Pos=%d,%d\n", settings->Pos.x, settings->Pos.y); + if (settings->Size.x != 0 || settings->Size.y != 0) + buf->appendf("Size=%d,%d\n", settings->Size.x, settings->Size.y); + buf->appendf("Collapsed=%d\n", settings->Collapsed); + if (settings->DockId != 0) + { + //buf->appendf("TabId=0x%08X\n", ImHashStr("#TAB", 4, settings->ID)); // window->TabId: this is not read back but writing it makes "debugging" the .ini data easier. + if (settings->DockOrder == -1) + buf->appendf("DockId=0x%08X\n", settings->DockId); + else + buf->appendf("DockId=0x%08X,%d\n", settings->DockId, settings->DockOrder); + if (settings->ClassId != 0) + buf->appendf("ClassId=0x%08X\n", settings->ClassId); + } } buf->append("\n"); } @@ -14095,9 +15510,28 @@ void ImGui::LocalizeRegisterEntries(const ImGuiLocEntry* entries, int count) // [SECTION] VIEWPORTS, PLATFORM WINDOWS //----------------------------------------------------------------------------- // - GetMainViewport() +// - FindViewportByID() +// - FindViewportByPlatformHandle() +// - SetCurrentViewport() [Internal] // - SetWindowViewport() [Internal] +// - GetWindowAlwaysWantOwnViewport() [Internal] +// - UpdateTryMergeWindowIntoHostViewport() [Internal] +// - UpdateTryMergeWindowIntoHostViewports() [Internal] +// - TranslateWindowsInViewport() [Internal] +// - ScaleWindowsInViewport() [Internal] +// - FindHoveredViewportFromPlatformWindowStack() [Internal] // - UpdateViewportsNewFrame() [Internal] -// (this section is more complete in the 'docking' branch) +// - UpdateViewportsEndFrame() [Internal] +// - AddUpdateViewport() [Internal] +// - WindowSelectViewport() [Internal] +// - WindowSyncOwnedViewport() [Internal] +// - UpdatePlatformWindows() +// - RenderPlatformWindowsDefault() +// - FindPlatformMonitorForPos() [Internal] +// - FindPlatformMonitorForRect() [Internal] +// - UpdateViewportPlatformMonitor() [Internal] +// - DestroyPlatformWindow() [Internal] +// - DestroyPlatformWindows() //----------------------------------------------------------------------------- ImGuiViewport* ImGui::GetMainViewport() @@ -14106,84 +15540,4984 @@ ImGuiViewport* ImGui::GetMainViewport() return g.Viewports[0]; } +// FIXME: This leaks access to viewports not listed in PlatformIO.Viewports[]. Problematic? (#4236) +ImGuiViewport* ImGui::FindViewportByID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + for (ImGuiViewportP* viewport : g.Viewports) + if (viewport->ID == id) + return viewport; + return NULL; +} + +ImGuiViewport* ImGui::FindViewportByPlatformHandle(void* platform_handle) +{ + ImGuiContext& g = *GImGui; + for (ImGuiViewportP* viewport : g.Viewports) + if (viewport->PlatformHandle == platform_handle) + return viewport; + return NULL; +} + +void ImGui::SetCurrentViewport(ImGuiWindow* current_window, ImGuiViewportP* viewport) +{ + ImGuiContext& g = *GImGui; + (void)current_window; + + if (viewport) + viewport->LastFrameActive = g.FrameCount; + if (g.CurrentViewport == viewport) + return; + g.CurrentDpiScale = viewport ? viewport->DpiScale : 1.0f; + g.CurrentViewport = viewport; + IM_ASSERT(g.CurrentDpiScale > 0.0f && g.CurrentDpiScale < 99.0f); // Typical correct values would be between 1.0f and 4.0f + //IMGUI_DEBUG_LOG_VIEWPORT("[viewport] SetCurrentViewport changed '%s' 0x%08X\n", current_window ? current_window->Name : NULL, viewport ? viewport->ID : 0); + + // Notify platform layer of viewport changes + // FIXME-DPI: This is only currently used for experimenting with handling of multiple DPI + if (g.CurrentViewport && g.PlatformIO.Platform_OnChangedViewport) + g.PlatformIO.Platform_OnChangedViewport(g.CurrentViewport); +} + void ImGui::SetWindowViewport(ImGuiWindow* window, ImGuiViewportP* viewport) { + // Abandon viewport + if (window->ViewportOwned && window->Viewport->Window == window) + window->Viewport->Size = ImVec2(0.0f, 0.0f); + window->Viewport = viewport; + window->ViewportId = viewport->ID; + window->ViewportOwned = (viewport->Window == window); } -// Update viewports and monitor infos -static void ImGui::UpdateViewportsNewFrame() +static bool ImGui::GetWindowAlwaysWantOwnViewport(ImGuiWindow* window) { + // Tooltips and menus are not automatically forced into their own viewport when the NoMerge flag is set, however the multiplication of viewports makes them more likely to protrude and create their own. ImGuiContext& g = *GImGui; - IM_ASSERT(g.Viewports.Size == 1); + if (g.IO.ConfigViewportsNoAutoMerge || (window->WindowClass.ViewportFlagsOverrideSet & ImGuiViewportFlags_NoAutoMerge)) + if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) + if (!window->DockIsActive) + if ((window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip)) == 0) + if ((window->Flags & ImGuiWindowFlags_Popup) == 0 || (window->Flags & ImGuiWindowFlags_Modal) != 0) + return true; + return false; +} - // Update main viewport with current platform position. - // FIXME-VIEWPORT: Size is driven by backend/user code for backward-compatibility but we should aim to make this more consistent. - ImGuiViewportP* main_viewport = g.Viewports[0]; - main_viewport->Flags = ImGuiViewportFlags_IsPlatformWindow | ImGuiViewportFlags_OwnedByApp; - main_viewport->Pos = ImVec2(0.0f, 0.0f); - main_viewport->Size = g.IO.DisplaySize; +static bool ImGui::UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImGuiViewportP* viewport) +{ + ImGuiContext& g = *GImGui; + if (window->Viewport == viewport) + return false; + if ((viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows) == 0) + return false; + if ((viewport->Flags & ImGuiViewportFlags_IsMinimized) != 0) + return false; + if (!viewport->GetMainRect().Contains(window->Rect())) + return false; + if (GetWindowAlwaysWantOwnViewport(window)) + return false; - for (ImGuiViewportP* viewport : g.Viewports) + // FIXME: Can't use g.WindowsFocusOrder[] for root windows only as we care about Z order. If we maintained a DisplayOrder along with FocusOrder we could.. + for (ImGuiWindow* window_behind : g.Windows) { - // Lock down space taken by menu bars and status bars, reset the offset for fucntions like BeginMainMenuBar() to alter them again. - viewport->WorkOffsetMin = viewport->BuildWorkOffsetMin; - viewport->WorkOffsetMax = viewport->BuildWorkOffsetMax; - viewport->BuildWorkOffsetMin = viewport->BuildWorkOffsetMax = ImVec2(0.0f, 0.0f); - viewport->UpdateWorkRect(); + if (window_behind == window) + break; + if (window_behind->WasActive && window_behind->ViewportOwned && !(window_behind->Flags & ImGuiWindowFlags_ChildWindow)) + if (window_behind->Viewport->GetMainRect().Overlaps(window->Rect())) + return false; } -} - -//----------------------------------------------------------------------------- -// [SECTION] DOCKING -//----------------------------------------------------------------------------- -// (this section is filled in the 'docking' branch) + // Move to the existing viewport, Move child/hosted windows as well (FIXME-OPT: iterate child) + ImGuiViewportP* old_viewport = window->Viewport; + if (window->ViewportOwned) + for (int n = 0; n < g.Windows.Size; n++) + if (g.Windows[n]->Viewport == old_viewport) + SetWindowViewport(g.Windows[n], viewport); + SetWindowViewport(window, viewport); + BringWindowToDisplayFront(window); + return true; +} -//----------------------------------------------------------------------------- -// [SECTION] PLATFORM DEPENDENT HELPERS -//----------------------------------------------------------------------------- +// FIXME: handle 0 to N host viewports +static bool ImGui::UpdateTryMergeWindowIntoHostViewports(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + return UpdateTryMergeWindowIntoHostViewport(window, g.Viewports[0]); +} -#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) +// Translate Dear ImGui windows when a Host Viewport has been moved +// (This additionally keeps windows at the same place when ImGuiConfigFlags_ViewportsEnable is toggled!) +void ImGui::TranslateWindowsInViewport(ImGuiViewportP* viewport, const ImVec2& old_pos, const ImVec2& new_pos, const ImVec2& old_size, const ImVec2& new_size) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(viewport->Window == NULL && (viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows)); -#ifdef _MSC_VER -#pragma comment(lib, "user32") -#pragma comment(lib, "kernel32") -#endif + // 1) We test if ImGuiConfigFlags_ViewportsEnable was just toggled, which allows us to conveniently + // translate imgui windows from OS-window-local to absolute coordinates or vice-versa. + // 2) If it's not going to fit into the new size, keep it at same absolute position. + // One problem with this is that most Win32 applications doesn't update their render while dragging, + // and so the window will appear to teleport when releasing the mouse. + const bool translate_all_windows = (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) != (g.ConfigFlagsLastFrame & ImGuiConfigFlags_ViewportsEnable); + ImRect test_still_fit_rect(old_pos, old_pos + viewport->Size); + ImVec2 delta_pos = new_pos - old_pos; + for (ImGuiWindow* window : g.Windows) // FIXME-OPT + if (translate_all_windows || (window->Viewport == viewport && (old_size == new_size || test_still_fit_rect.Contains(window->Rect())))) + TranslateWindow(window, delta_pos); +} -// Win32 clipboard implementation -// We use g.ClipboardHandlerData for temporary storage to ensure it is freed on Shutdown() -static const char* GetClipboardTextFn_DefaultImpl(void* user_data_ctx) +// Scale all windows (position, size). Use when e.g. changing DPI. (This is a lossy operation!) +void ImGui::ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale) { - ImGuiContext& g = *(ImGuiContext*)user_data_ctx; - g.ClipboardHandlerData.clear(); - if (!::OpenClipboard(NULL)) - return NULL; - HANDLE wbuf_handle = ::GetClipboardData(CF_UNICODETEXT); - if (wbuf_handle == NULL) + ImGuiContext& g = *GImGui; + if (viewport->Window) { - ::CloseClipboard(); - return NULL; + ScaleWindow(viewport->Window, scale); } - if (const WCHAR* wbuf_global = (const WCHAR*)::GlobalLock(wbuf_handle)) + else { - int buf_len = ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, NULL, 0, NULL, NULL); - g.ClipboardHandlerData.resize(buf_len); - ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, g.ClipboardHandlerData.Data, buf_len, NULL, NULL); + for (ImGuiWindow* window : g.Windows) + if (window->Viewport == viewport) + ScaleWindow(window, scale); } - ::GlobalUnlock(wbuf_handle); - ::CloseClipboard(); - return g.ClipboardHandlerData.Data; } -static void SetClipboardTextFn_DefaultImpl(void*, const char* text) +// If the backend doesn't set MouseLastHoveredViewport or doesn't honor ImGuiViewportFlags_NoInputs, we do a search ourselves. +// A) It won't take account of the possibility that non-imgui windows may be in-between our dragged window and our target window. +// B) It requires Platform_GetWindowFocus to be implemented by backend. +ImGuiViewportP* ImGui::FindHoveredViewportFromPlatformWindowStack(const ImVec2& mouse_platform_pos) { - if (!::OpenClipboard(NULL)) - return; - const int wbuf_length = ::MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0); - HGLOBAL wbuf_handle = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(WCHAR)); - if (wbuf_handle == NULL) + ImGuiContext& g = *GImGui; + ImGuiViewportP* best_candidate = NULL; + for (ImGuiViewportP* viewport : g.Viewports) + if (!(viewport->Flags & (ImGuiViewportFlags_NoInputs | ImGuiViewportFlags_IsMinimized)) && viewport->GetMainRect().Contains(mouse_platform_pos)) + if (best_candidate == NULL || best_candidate->LastFocusedStampCount < viewport->LastFocusedStampCount) + best_candidate = viewport; + return best_candidate; +} + +// Update viewports and monitor infos +// Note that this is running even if 'ImGuiConfigFlags_ViewportsEnable' is not set, in order to clear unused viewports (if any) and update monitor info. +static void ImGui::UpdateViewportsNewFrame() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.PlatformIO.Viewports.Size <= g.Viewports.Size); + + // Update Minimized status (we need it first in order to decide if we'll apply Pos/Size of the main viewport) + // Update Focused status + const bool viewports_enabled = (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) != 0; + if (viewports_enabled) + { + ImGuiViewportP* focused_viewport = NULL; + for (ImGuiViewportP* viewport : g.Viewports) + { + const bool platform_funcs_available = viewport->PlatformWindowCreated; + if (g.PlatformIO.Platform_GetWindowMinimized && platform_funcs_available) + { + bool is_minimized = g.PlatformIO.Platform_GetWindowMinimized(viewport); + if (is_minimized) + viewport->Flags |= ImGuiViewportFlags_IsMinimized; + else + viewport->Flags &= ~ImGuiViewportFlags_IsMinimized; + } + + // Update our implicit z-order knowledge of platform windows, which is used when the backend cannot provide io.MouseHoveredViewport. + // When setting Platform_GetWindowFocus, it is expected that the platform backend can handle calls without crashing if it doesn't have data stored. + if (g.PlatformIO.Platform_GetWindowFocus && platform_funcs_available) + { + bool is_focused = g.PlatformIO.Platform_GetWindowFocus(viewport); + if (is_focused) + viewport->Flags |= ImGuiViewportFlags_IsFocused; + else + viewport->Flags &= ~ImGuiViewportFlags_IsFocused; + if (is_focused) + focused_viewport = viewport; + } + } + + // Focused viewport has changed? + if (focused_viewport && g.PlatformLastFocusedViewportId != focused_viewport->ID) + { + IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Focused viewport changed %08X -> %08X, attempting to apply our focus.\n", g.PlatformLastFocusedViewportId, focused_viewport->ID); + const ImGuiViewport* prev_focused_viewport = FindViewportByID(g.PlatformLastFocusedViewportId); + const bool prev_focused_has_been_destroyed = (prev_focused_viewport == NULL) || (prev_focused_viewport->PlatformWindowCreated == false); + + // Store a tag so we can infer z-order easily from all our windows + // We compare PlatformLastFocusedViewportId so newly created viewports with _NoFocusOnAppearing flag + // will keep the front most stamp instead of losing it back to their parent viewport. + if (focused_viewport->LastFocusedStampCount != g.ViewportFocusedStampCount) + focused_viewport->LastFocusedStampCount = ++g.ViewportFocusedStampCount; + g.PlatformLastFocusedViewportId = focused_viewport->ID; + + // Focus associated dear imgui window + // - if focus didn't happen with a click within imgui boundaries, e.g. Clicking platform title bar. (#6299) + // - if focus didn't happen because we destroyed another window (#6462) + // FIXME: perhaps 'FocusTopMostWindowUnderOne()' can handle the 'focused_window->Window != NULL' case as well. + const bool apply_imgui_focus_on_focused_viewport = !IsAnyMouseDown() && !prev_focused_has_been_destroyed; + if (apply_imgui_focus_on_focused_viewport) + { + focused_viewport->LastFocusedHadNavWindow |= (g.NavWindow != NULL) && (g.NavWindow->Viewport == focused_viewport); // Update so a window changing viewport won't lose focus. + ImGuiFocusRequestFlags focus_request_flags = ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild; + if (focused_viewport->Window != NULL) + FocusWindow(focused_viewport->Window, focus_request_flags); + else if (focused_viewport->LastFocusedHadNavWindow) + FocusTopMostWindowUnderOne(NULL, NULL, focused_viewport, focus_request_flags); // Focus top most in viewport + else + FocusWindow(NULL, focus_request_flags); // No window had focus last time viewport was focused + } + } + if (focused_viewport) + focused_viewport->LastFocusedHadNavWindow = (g.NavWindow != NULL) && (g.NavWindow->Viewport == focused_viewport); + } + + // Create/update main viewport with current platform position. + // FIXME-VIEWPORT: Size is driven by backend/user code for backward-compatibility but we should aim to make this more consistent. + ImGuiViewportP* main_viewport = g.Viewports[0]; + IM_ASSERT(main_viewport->ID == IMGUI_VIEWPORT_DEFAULT_ID); + IM_ASSERT(main_viewport->Window == NULL); + ImVec2 main_viewport_pos = viewports_enabled ? g.PlatformIO.Platform_GetWindowPos(main_viewport) : ImVec2(0.0f, 0.0f); + ImVec2 main_viewport_size = g.IO.DisplaySize; + if (viewports_enabled && (main_viewport->Flags & ImGuiViewportFlags_IsMinimized)) + { + main_viewport_pos = main_viewport->Pos; // Preserve last pos/size when minimized (FIXME: We don't do the same for Size outside of the viewport path) + main_viewport_size = main_viewport->Size; + } + AddUpdateViewport(NULL, IMGUI_VIEWPORT_DEFAULT_ID, main_viewport_pos, main_viewport_size, ImGuiViewportFlags_OwnedByApp | ImGuiViewportFlags_CanHostOtherWindows); + + g.CurrentDpiScale = 0.0f; + g.CurrentViewport = NULL; + g.MouseViewport = NULL; + for (int n = 0; n < g.Viewports.Size; n++) + { + ImGuiViewportP* viewport = g.Viewports[n]; + viewport->Idx = n; + + // Erase unused viewports + if (n > 0 && viewport->LastFrameActive < g.FrameCount - 2) + { + DestroyViewport(viewport); + n--; + continue; + } + + const bool platform_funcs_available = viewport->PlatformWindowCreated; + if (viewports_enabled) + { + // Update Position and Size (from Platform Window to ImGui) if requested. + // We do it early in the frame instead of waiting for UpdatePlatformWindows() to avoid a frame of lag when moving/resizing using OS facilities. + if (!(viewport->Flags & ImGuiViewportFlags_IsMinimized) && platform_funcs_available) + { + // Viewport->WorkPos and WorkSize will be updated below + if (viewport->PlatformRequestMove) + viewport->Pos = viewport->LastPlatformPos = g.PlatformIO.Platform_GetWindowPos(viewport); + if (viewport->PlatformRequestResize) + viewport->Size = viewport->LastPlatformSize = g.PlatformIO.Platform_GetWindowSize(viewport); + } + } + + // Update/copy monitor info + UpdateViewportPlatformMonitor(viewport); + + // Lock down space taken by menu bars and status bars + query initial insets from backend + // Setup initial value for functions like BeginMainMenuBar(), DockSpaceOverViewport() etc. + viewport->WorkInsetMin = viewport->BuildWorkInsetMin; + viewport->WorkInsetMax = viewport->BuildWorkInsetMax; + viewport->BuildWorkInsetMin = viewport->BuildWorkInsetMax = ImVec2(0.0f, 0.0f); + if (g.PlatformIO.Platform_GetWindowWorkAreaInsets != NULL && platform_funcs_available) + { + ImVec4 insets = g.PlatformIO.Platform_GetWindowWorkAreaInsets(viewport); + IM_ASSERT(insets.x >= 0.0f && insets.y >= 0.0f && insets.z >= 0.0f && insets.w >= 0.0f); + viewport->BuildWorkInsetMin = ImVec2(insets.x, insets.y); + viewport->BuildWorkInsetMax = ImVec2(insets.z, insets.w); + } + viewport->UpdateWorkRect(); + + // Reset alpha every frame. Users of transparency (docking) needs to request a lower alpha back. + viewport->Alpha = 1.0f; + + // Translate Dear ImGui windows when a Host Viewport has been moved + // (This additionally keeps windows at the same place when ImGuiConfigFlags_ViewportsEnable is toggled!) + const ImVec2 viewport_delta_pos = viewport->Pos - viewport->LastPos; + if ((viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows) && (viewport_delta_pos.x != 0.0f || viewport_delta_pos.y != 0.0f)) + TranslateWindowsInViewport(viewport, viewport->LastPos, viewport->Pos, viewport->LastSize, viewport->Size); + + // Update DPI scale + float new_dpi_scale; + if (g.PlatformIO.Platform_GetWindowDpiScale && platform_funcs_available) + new_dpi_scale = g.PlatformIO.Platform_GetWindowDpiScale(viewport); + else if (viewport->PlatformMonitor != -1) + new_dpi_scale = g.PlatformIO.Monitors[viewport->PlatformMonitor].DpiScale; + else + new_dpi_scale = (viewport->DpiScale != 0.0f) ? viewport->DpiScale : 1.0f; + IM_ASSERT(new_dpi_scale > 0.0f && new_dpi_scale < 99.0f); // Typical correct values would be between 1.0f and 4.0f + if (viewport->DpiScale != 0.0f && new_dpi_scale != viewport->DpiScale) + { + float scale_factor = new_dpi_scale / viewport->DpiScale; + if (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleViewports) + ScaleWindowsInViewport(viewport, scale_factor); + //if (viewport == GetMainViewport()) + // g.PlatformInterface.SetWindowSize(viewport, viewport->Size * scale_factor); + + // Scale our window moving pivot so that the window will rescale roughly around the mouse position. + // FIXME-VIEWPORT: This currently creates a resizing feedback loop when a window is straddling a DPI transition border. + // (Minor: since our sizes do not perfectly linearly scale, deferring the click offset scale until we know the actual window scale ratio may get us slightly more precise mouse positioning.) + //if (g.MovingWindow != NULL && g.MovingWindow->Viewport == viewport) + // g.ActiveIdClickOffset = ImTrunc(g.ActiveIdClickOffset * scale_factor); + } + viewport->DpiScale = new_dpi_scale; + } + + // Update fallback monitor + g.PlatformMonitorsFullWorkRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); + if (g.PlatformIO.Monitors.Size == 0) + { + ImGuiPlatformMonitor* monitor = &g.FallbackMonitor; + monitor->MainPos = main_viewport->Pos; + monitor->MainSize = main_viewport->Size; + monitor->WorkPos = main_viewport->WorkPos; + monitor->WorkSize = main_viewport->WorkSize; + monitor->DpiScale = main_viewport->DpiScale; + g.PlatformMonitorsFullWorkRect.Add(monitor->WorkPos); + g.PlatformMonitorsFullWorkRect.Add(monitor->WorkPos + monitor->WorkSize); + } + else + { + g.FallbackMonitor = g.PlatformIO.Monitors[0]; + } + for (ImGuiPlatformMonitor& monitor : g.PlatformIO.Monitors) + { + g.PlatformMonitorsFullWorkRect.Add(monitor.WorkPos); + g.PlatformMonitorsFullWorkRect.Add(monitor.WorkPos + monitor.WorkSize); + } + + if (!viewports_enabled) + { + g.MouseViewport = main_viewport; + return; + } + + // Mouse handling: decide on the actual mouse viewport for this frame between the active/focused viewport and the hovered viewport. + // Note that 'viewport_hovered' should skip over any viewport that has the ImGuiViewportFlags_NoInputs flags set. + ImGuiViewportP* viewport_hovered = NULL; + if (g.IO.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport) + { + viewport_hovered = g.IO.MouseHoveredViewport ? (ImGuiViewportP*)FindViewportByID(g.IO.MouseHoveredViewport) : NULL; + if (viewport_hovered && (viewport_hovered->Flags & ImGuiViewportFlags_NoInputs)) + viewport_hovered = FindHoveredViewportFromPlatformWindowStack(g.IO.MousePos); // Backend failed to handle _NoInputs viewport: revert to our fallback. + } + else + { + // If the backend doesn't know how to honor ImGuiViewportFlags_NoInputs, we do a search ourselves. Note that this search: + // A) won't take account of the possibility that non-imgui windows may be in-between our dragged window and our target window. + // B) won't take account of how the backend apply parent<>child relationship to secondary viewports, which affects their Z order. + // C) uses LastFrameAsRefViewport as a flawed replacement for the last time a window was focused (we could/should fix that by introducing Focus functions in PlatformIO) + viewport_hovered = FindHoveredViewportFromPlatformWindowStack(g.IO.MousePos); + } + if (viewport_hovered != NULL) + g.MouseLastHoveredViewport = viewport_hovered; + else if (g.MouseLastHoveredViewport == NULL) + g.MouseLastHoveredViewport = g.Viewports[0]; + + // Update mouse reference viewport + // (when moving a window we aim at its viewport, but this will be overwritten below if we go in drag and drop mode) + // (MovingViewport->Viewport will be NULL in the rare situation where the window disappared while moving, set UpdateMouseMovingWindowNewFrame() for details) + if (g.MovingWindow && g.MovingWindow->Viewport) + g.MouseViewport = g.MovingWindow->Viewport; + else + g.MouseViewport = g.MouseLastHoveredViewport; + + // When dragging something, always refer to the last hovered viewport. + // - when releasing a moving window we will revert to aiming behind (at viewport_hovered) + // - when we are between viewports, our dragged preview will tend to show in the last viewport _even_ if we don't have tooltips in their viewports (when lacking monitor info) + // - consider the case of holding on a menu item to browse child menus: even thou a mouse button is held, there's no active id because menu items only react on mouse release. + // FIXME-VIEWPORT: This is essentially broken, when ImGuiBackendFlags_HasMouseHoveredViewport is set we want to trust when viewport_hovered==NULL and use that. + const bool is_mouse_dragging_with_an_expected_destination = g.DragDropActive; + if (is_mouse_dragging_with_an_expected_destination && viewport_hovered == NULL) + viewport_hovered = g.MouseLastHoveredViewport; + if (is_mouse_dragging_with_an_expected_destination || g.ActiveId == 0 || !IsAnyMouseDown()) + if (viewport_hovered != NULL && viewport_hovered != g.MouseViewport && !(viewport_hovered->Flags & ImGuiViewportFlags_NoInputs)) + g.MouseViewport = viewport_hovered; + + IM_ASSERT(g.MouseViewport != NULL); +} + +// Update user-facing viewport list (g.Viewports -> g.PlatformIO.Viewports after filtering out some) +static void ImGui::UpdateViewportsEndFrame() +{ + ImGuiContext& g = *GImGui; + g.PlatformIO.Viewports.resize(0); + for (int i = 0; i < g.Viewports.Size; i++) + { + ImGuiViewportP* viewport = g.Viewports[i]; + viewport->LastPos = viewport->Pos; + viewport->LastSize = viewport->Size; + if (viewport->LastFrameActive < g.FrameCount || viewport->Size.x <= 0.0f || viewport->Size.y <= 0.0f) + if (i > 0) // Always include main viewport in the list + continue; + if (viewport->Window && !IsWindowActiveAndVisible(viewport->Window)) + continue; + if (i > 0) + IM_ASSERT(viewport->Window != NULL); + g.PlatformIO.Viewports.push_back(viewport); + } + g.Viewports[0]->ClearRequestFlags(); // Clear main viewport flags because UpdatePlatformWindows() won't do it and may not even be called +} + +// FIXME: We should ideally refactor the system to call this every frame (we currently don't) +ImGuiViewportP* ImGui::AddUpdateViewport(ImGuiWindow* window, ImGuiID id, const ImVec2& pos, const ImVec2& size, ImGuiViewportFlags flags) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(id != 0); + + flags |= ImGuiViewportFlags_IsPlatformWindow; + if (window != NULL) + { + if (g.MovingWindow && g.MovingWindow->RootWindowDockTree == window) + flags |= ImGuiViewportFlags_NoInputs | ImGuiViewportFlags_NoFocusOnAppearing; + if ((window->Flags & ImGuiWindowFlags_NoMouseInputs) && (window->Flags & ImGuiWindowFlags_NoNavInputs)) + flags |= ImGuiViewportFlags_NoInputs; + if (window->Flags & ImGuiWindowFlags_NoFocusOnAppearing) + flags |= ImGuiViewportFlags_NoFocusOnAppearing; + } + + ImGuiViewportP* viewport = (ImGuiViewportP*)FindViewportByID(id); + if (viewport) + { + // Always update for main viewport as we are already pulling correct platform pos/size (see #4900) + if (!viewport->PlatformRequestMove || viewport->ID == IMGUI_VIEWPORT_DEFAULT_ID) + viewport->Pos = pos; + if (!viewport->PlatformRequestResize || viewport->ID == IMGUI_VIEWPORT_DEFAULT_ID) + viewport->Size = size; + viewport->Flags = flags | (viewport->Flags & (ImGuiViewportFlags_IsMinimized | ImGuiViewportFlags_IsFocused)); // Preserve existing flags + } + else + { + // New viewport + viewport = IM_NEW(ImGuiViewportP)(); + viewport->ID = id; + viewport->Idx = g.Viewports.Size; + viewport->Pos = viewport->LastPos = pos; + viewport->Size = viewport->LastSize = size; + viewport->Flags = flags; + UpdateViewportPlatformMonitor(viewport); + g.Viewports.push_back(viewport); + g.ViewportCreatedCount++; + IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Add Viewport %08X '%s'\n", id, window ? window->Name : ""); + + // We normally setup for all viewports in NewFrame() but here need to handle the mid-frame creation of a new viewport. + // We need to extend the fullscreen clip rect so the OverlayDrawList clip is correct for that the first frame + g.DrawListSharedData.ClipRectFullscreen.x = ImMin(g.DrawListSharedData.ClipRectFullscreen.x, viewport->Pos.x); + g.DrawListSharedData.ClipRectFullscreen.y = ImMin(g.DrawListSharedData.ClipRectFullscreen.y, viewport->Pos.y); + g.DrawListSharedData.ClipRectFullscreen.z = ImMax(g.DrawListSharedData.ClipRectFullscreen.z, viewport->Pos.x + viewport->Size.x); + g.DrawListSharedData.ClipRectFullscreen.w = ImMax(g.DrawListSharedData.ClipRectFullscreen.w, viewport->Pos.y + viewport->Size.y); + + // Store initial DpiScale before the OS platform window creation, based on expected monitor data. + // This is so we can select an appropriate font size on the first frame of our window lifetime + if (viewport->PlatformMonitor != -1) + viewport->DpiScale = g.PlatformIO.Monitors[viewport->PlatformMonitor].DpiScale; + } + + viewport->Window = window; + viewport->LastFrameActive = g.FrameCount; + viewport->UpdateWorkRect(); + IM_ASSERT(window == NULL || viewport->ID == window->ID); + + if (window != NULL) + window->ViewportOwned = true; + + return viewport; +} + +static void ImGui::DestroyViewport(ImGuiViewportP* viewport) +{ + // Clear references to this viewport in windows (window->ViewportId becomes the master data) + ImGuiContext& g = *GImGui; + for (ImGuiWindow* window : g.Windows) + { + if (window->Viewport != viewport) + continue; + window->Viewport = NULL; + window->ViewportOwned = false; + } + if (viewport == g.MouseLastHoveredViewport) + g.MouseLastHoveredViewport = NULL; + + // Destroy + IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Delete Viewport %08X '%s'\n", viewport->ID, viewport->Window ? viewport->Window->Name : "n/a"); + DestroyPlatformWindow(viewport); // In most circumstances the platform window will already be destroyed here. + IM_ASSERT(g.PlatformIO.Viewports.contains(viewport) == false); + IM_ASSERT(g.Viewports[viewport->Idx] == viewport); + g.Viewports.erase(g.Viewports.Data + viewport->Idx); + IM_DELETE(viewport); +} + +// FIXME-VIEWPORT: This is all super messy and ought to be clarified or rewritten. +static void ImGui::WindowSelectViewport(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + ImGuiWindowFlags flags = window->Flags; + window->ViewportAllowPlatformMonitorExtend = -1; + + // Restore main viewport if multi-viewport is not supported by the backend + ImGuiViewportP* main_viewport = (ImGuiViewportP*)(void*)GetMainViewport(); + if (!(g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)) + { + SetWindowViewport(window, main_viewport); + return; + } + window->ViewportOwned = false; + + // Appearing popups reset their viewport so they can inherit again + if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && window->Appearing) + { + window->Viewport = NULL; + window->ViewportId = 0; + } + + if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasViewport) == 0) + { + // By default inherit from parent window + if (window->Viewport == NULL && window->ParentWindow && (!window->ParentWindow->IsFallbackWindow || window->ParentWindow->WasActive)) + window->Viewport = window->ParentWindow->Viewport; + + // Attempt to restore saved viewport id (= window that hasn't been activated yet), try to restore the viewport based on saved 'window->ViewportPos' restored from .ini file + if (window->Viewport == NULL && window->ViewportId != 0) + { + window->Viewport = (ImGuiViewportP*)FindViewportByID(window->ViewportId); + if (window->Viewport == NULL && window->ViewportPos.x != FLT_MAX && window->ViewportPos.y != FLT_MAX) + window->Viewport = AddUpdateViewport(window, window->ID, window->ViewportPos, window->Size, ImGuiViewportFlags_None); + } + } + + bool lock_viewport = false; + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasViewport) + { + // Code explicitly request a viewport + window->Viewport = (ImGuiViewportP*)FindViewportByID(g.NextWindowData.ViewportId); + window->ViewportId = g.NextWindowData.ViewportId; // Store ID even if Viewport isn't resolved yet. + if (window->Viewport && (window->Flags & ImGuiWindowFlags_DockNodeHost) != 0 && window->Viewport->Window != NULL) + { + window->Viewport->Window = window; + window->Viewport->ID = window->ViewportId = window->ID; // Overwrite ID (always owned by node) + } + lock_viewport = true; + } + else if ((flags & ImGuiWindowFlags_ChildWindow) || (flags & ImGuiWindowFlags_ChildMenu)) + { + // Always inherit viewport from parent window + if (window->DockNode && window->DockNode->HostWindow) + IM_ASSERT(window->DockNode->HostWindow->Viewport == window->ParentWindow->Viewport); + window->Viewport = window->ParentWindow->Viewport; + } + else if (window->DockNode && window->DockNode->HostWindow) + { + // This covers the "always inherit viewport from parent window" case for when a window reattach to a node that was just created mid-frame + window->Viewport = window->DockNode->HostWindow->Viewport; + } + else if (flags & ImGuiWindowFlags_Tooltip) + { + window->Viewport = g.MouseViewport; + } + else if (GetWindowAlwaysWantOwnViewport(window)) + { + window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_None); + } + else if (g.MovingWindow && g.MovingWindow->RootWindowDockTree == window && IsMousePosValid()) + { + if (window->Viewport != NULL && window->Viewport->Window == window) + window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_None); + } + else + { + // Merge into host viewport? + // We cannot test window->ViewportOwned as it set lower in the function. + // Testing (g.ActiveId == 0 || g.ActiveIdAllowOverlap) to avoid merging during a short-term widget interaction. Main intent was to avoid during resize (see #4212) + bool try_to_merge_into_host_viewport = (window->Viewport && window == window->Viewport->Window && (g.ActiveId == 0 || g.ActiveIdAllowOverlap)); + if (try_to_merge_into_host_viewport) + UpdateTryMergeWindowIntoHostViewports(window); + } + + // Fallback: merge in default viewport if z-order matches, otherwise create a new viewport + if (window->Viewport == NULL) + if (!UpdateTryMergeWindowIntoHostViewport(window, main_viewport)) + window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_None); + + // Mark window as allowed to protrude outside of its viewport and into the current monitor + if (!lock_viewport) + { + if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) + { + // We need to take account of the possibility that mouse may become invalid. + // Popups/Tooltip always set ViewportAllowPlatformMonitorExtend so GetWindowAllowedExtentRect() will return full monitor bounds. + ImVec2 mouse_ref = (flags & ImGuiWindowFlags_Tooltip) ? g.IO.MousePos : g.BeginPopupStack.back().OpenMousePos; + bool use_mouse_ref = (!g.NavCursorVisible || !g.NavHighlightItemUnderNav || !g.NavWindow); + bool mouse_valid = IsMousePosValid(&mouse_ref); + if ((window->Appearing || (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_ChildMenu))) && (!use_mouse_ref || mouse_valid)) + window->ViewportAllowPlatformMonitorExtend = FindPlatformMonitorForPos((use_mouse_ref && mouse_valid) ? mouse_ref : NavCalcPreferredRefPos()); + else + window->ViewportAllowPlatformMonitorExtend = window->Viewport->PlatformMonitor; + } + else if (window->Viewport && window != window->Viewport->Window && window->Viewport->Window && !(flags & ImGuiWindowFlags_ChildWindow) && window->DockNode == NULL) + { + // When called from Begin() we don't have access to a proper version of the Hidden flag yet, so we replicate this code. + const bool will_be_visible = (window->DockIsActive && !window->DockTabIsVisible) ? false : true; + if ((window->Flags & ImGuiWindowFlags_DockNodeHost) && window->Viewport->LastFrameActive < g.FrameCount && will_be_visible) + { + // Steal/transfer ownership + IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Window '%s' steal Viewport %08X from Window '%s'\n", window->Name, window->Viewport->ID, window->Viewport->Window->Name); + window->Viewport->Window = window; + window->Viewport->ID = window->ID; + window->Viewport->LastNameHash = 0; + } + else if (!UpdateTryMergeWindowIntoHostViewports(window)) // Merge? + { + // New viewport + window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_NoFocusOnAppearing); + } + } + else if (window->ViewportAllowPlatformMonitorExtend < 0 && (flags & ImGuiWindowFlags_ChildWindow) == 0) + { + // Regular (non-child, non-popup) windows by default are also allowed to protrude + // Child windows are kept contained within their parent. + window->ViewportAllowPlatformMonitorExtend = window->Viewport->PlatformMonitor; + } + } + + // Update flags + window->ViewportOwned = (window == window->Viewport->Window); + window->ViewportId = window->Viewport->ID; + + // If the OS window has a title bar, hide our imgui title bar + //if (window->ViewportOwned && !(window->Viewport->Flags & ImGuiViewportFlags_NoDecoration)) + // window->Flags |= ImGuiWindowFlags_NoTitleBar; +} + +void ImGui::WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_window_in_stack) +{ + ImGuiContext& g = *GImGui; + + bool viewport_rect_changed = false; + + // Synchronize window --> viewport in most situations + // Synchronize viewport -> window in case the platform window has been moved or resized from the OS/WM + if (window->Viewport->PlatformRequestMove) + { + window->Pos = window->Viewport->Pos; + MarkIniSettingsDirty(window); + } + else if (memcmp(&window->Viewport->Pos, &window->Pos, sizeof(window->Pos)) != 0) + { + viewport_rect_changed = true; + window->Viewport->Pos = window->Pos; + } + + if (window->Viewport->PlatformRequestResize) + { + window->Size = window->SizeFull = window->Viewport->Size; + MarkIniSettingsDirty(window); + } + else if (memcmp(&window->Viewport->Size, &window->Size, sizeof(window->Size)) != 0) + { + viewport_rect_changed = true; + window->Viewport->Size = window->Size; + } + window->Viewport->UpdateWorkRect(); + + // The viewport may have changed monitor since the global update in UpdateViewportsNewFrame() + // Either a SetNextWindowPos() call in the current frame or a SetWindowPos() call in the previous frame may have this effect. + if (viewport_rect_changed) + UpdateViewportPlatformMonitor(window->Viewport); + + // Update common viewport flags + const ImGuiViewportFlags viewport_flags_to_clear = ImGuiViewportFlags_TopMost | ImGuiViewportFlags_NoTaskBarIcon | ImGuiViewportFlags_NoDecoration | ImGuiViewportFlags_NoRendererClear; + ImGuiViewportFlags viewport_flags = window->Viewport->Flags & ~viewport_flags_to_clear; + ImGuiWindowFlags window_flags = window->Flags; + const bool is_modal = (window_flags & ImGuiWindowFlags_Modal) != 0; + const bool is_short_lived_floating_window = (window_flags & (ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) != 0; + if (window_flags & ImGuiWindowFlags_Tooltip) + viewport_flags |= ImGuiViewportFlags_TopMost; + if ((g.IO.ConfigViewportsNoTaskBarIcon || is_short_lived_floating_window) && !is_modal) + viewport_flags |= ImGuiViewportFlags_NoTaskBarIcon; + if (g.IO.ConfigViewportsNoDecoration || is_short_lived_floating_window) + viewport_flags |= ImGuiViewportFlags_NoDecoration; + + // Not correct to set modal as topmost because: + // - Because other popups can be stacked above a modal (e.g. combo box in a modal) + // - ImGuiViewportFlags_TopMost is currently handled different in backends: in Win32 it is "appear top most" whereas in GLFW and SDL it is "stay topmost" + //if (flags & ImGuiWindowFlags_Modal) + // viewport_flags |= ImGuiViewportFlags_TopMost; + + // For popups and menus that may be protruding out of their parent viewport, we enable _NoFocusOnClick so that clicking on them + // won't steal the OS focus away from their parent window (which may be reflected in OS the title bar decoration). + // Setting _NoFocusOnClick would technically prevent us from bringing back to front in case they are being covered by an OS window from a different app, + // but it shouldn't be much of a problem considering those are already popups that are closed when clicking elsewhere. + if (is_short_lived_floating_window && !is_modal) + viewport_flags |= ImGuiViewportFlags_NoFocusOnAppearing | ImGuiViewportFlags_NoFocusOnClick; + + // We can overwrite viewport flags using ImGuiWindowClass (advanced users) + if (window->WindowClass.ViewportFlagsOverrideSet) + viewport_flags |= window->WindowClass.ViewportFlagsOverrideSet; + if (window->WindowClass.ViewportFlagsOverrideClear) + viewport_flags &= ~window->WindowClass.ViewportFlagsOverrideClear; + + // We can also tell the backend that clearing the platform window won't be necessary, + // as our window background is filling the viewport and we have disabled BgAlpha. + // FIXME: Work on support for per-viewport transparency (#2766) + if (!(window_flags & ImGuiWindowFlags_NoBackground)) + viewport_flags |= ImGuiViewportFlags_NoRendererClear; + + window->Viewport->Flags = viewport_flags; + + // Update parent viewport ID + // (the !IsFallbackWindow test mimic the one done in WindowSelectViewport()) + if (window->WindowClass.ParentViewportId != (ImGuiID)-1) + window->Viewport->ParentViewportId = window->WindowClass.ParentViewportId; + else if ((window_flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && parent_window_in_stack && (!parent_window_in_stack->IsFallbackWindow || parent_window_in_stack->WasActive)) + window->Viewport->ParentViewportId = parent_window_in_stack->Viewport->ID; + else + window->Viewport->ParentViewportId = g.IO.ConfigViewportsNoDefaultParent ? 0 : IMGUI_VIEWPORT_DEFAULT_ID; +} + +// Called by user at the end of the main loop, after EndFrame() +// This will handle the creation/update of all OS windows via function defined in the ImGuiPlatformIO api. +void ImGui::UpdatePlatformWindows() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.FrameCountEnded == g.FrameCount && "Forgot to call Render() or EndFrame() before UpdatePlatformWindows()?"); + IM_ASSERT(g.FrameCountPlatformEnded < g.FrameCount); + g.FrameCountPlatformEnded = g.FrameCount; + if (!(g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)) + return; + + // Create/resize/destroy platform windows to match each active viewport. + // Skip the main viewport (index 0), which is always fully handled by the application! + for (int i = 1; i < g.Viewports.Size; i++) + { + ImGuiViewportP* viewport = g.Viewports[i]; + + // Destroy platform window if the viewport hasn't been submitted or if it is hosting a hidden window + // (the implicit/fallback Debug##Default window will be registering its viewport then be disabled, causing a dummy DestroyPlatformWindow to be made each frame) + bool destroy_platform_window = false; + destroy_platform_window |= (viewport->LastFrameActive < g.FrameCount - 1); + destroy_platform_window |= (viewport->Window && !IsWindowActiveAndVisible(viewport->Window)); + if (destroy_platform_window) + { + DestroyPlatformWindow(viewport); + continue; + } + + // New windows that appears directly in a new viewport won't always have a size on their first frame + if (viewport->LastFrameActive < g.FrameCount || viewport->Size.x <= 0 || viewport->Size.y <= 0) + continue; + + // Create window + const bool is_new_platform_window = (viewport->PlatformWindowCreated == false); + if (is_new_platform_window) + { + IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Create Platform Window %08X '%s'\n", viewport->ID, viewport->Window ? viewport->Window->Name : "n/a"); + g.PlatformIO.Platform_CreateWindow(viewport); + if (g.PlatformIO.Renderer_CreateWindow != NULL) + g.PlatformIO.Renderer_CreateWindow(viewport); + g.PlatformWindowsCreatedCount++; + viewport->LastNameHash = 0; + viewport->LastPlatformPos = viewport->LastPlatformSize = ImVec2(FLT_MAX, FLT_MAX); // By clearing those we'll enforce a call to Platform_SetWindowPos/Size below, before Platform_ShowWindow (FIXME: Is that necessary?) + viewport->LastRendererSize = viewport->Size; // We don't need to call Renderer_SetWindowSize() as it is expected Renderer_CreateWindow() already did it. + viewport->PlatformWindowCreated = true; + } + + // Apply Position and Size (from ImGui to Platform/Renderer backends) + if ((viewport->LastPlatformPos.x != viewport->Pos.x || viewport->LastPlatformPos.y != viewport->Pos.y) && !viewport->PlatformRequestMove) + g.PlatformIO.Platform_SetWindowPos(viewport, viewport->Pos); + if ((viewport->LastPlatformSize.x != viewport->Size.x || viewport->LastPlatformSize.y != viewport->Size.y) && !viewport->PlatformRequestResize) + g.PlatformIO.Platform_SetWindowSize(viewport, viewport->Size); + if ((viewport->LastRendererSize.x != viewport->Size.x || viewport->LastRendererSize.y != viewport->Size.y) && g.PlatformIO.Renderer_SetWindowSize) + g.PlatformIO.Renderer_SetWindowSize(viewport, viewport->Size); + viewport->LastPlatformPos = viewport->Pos; + viewport->LastPlatformSize = viewport->LastRendererSize = viewport->Size; + + // Update title bar (if it changed) + if (ImGuiWindow* window_for_title = GetWindowForTitleDisplay(viewport->Window)) + { + const char* title_begin = window_for_title->Name; + char* title_end = (char*)(intptr_t)FindRenderedTextEnd(title_begin); + const ImGuiID title_hash = ImHashStr(title_begin, title_end - title_begin); + if (viewport->LastNameHash != title_hash) + { + char title_end_backup_c = *title_end; + *title_end = 0; // Cut existing buffer short instead of doing an alloc/free, no small gain. + g.PlatformIO.Platform_SetWindowTitle(viewport, title_begin); + *title_end = title_end_backup_c; + viewport->LastNameHash = title_hash; + } + } + + // Update alpha (if it changed) + if (viewport->LastAlpha != viewport->Alpha && g.PlatformIO.Platform_SetWindowAlpha) + g.PlatformIO.Platform_SetWindowAlpha(viewport, viewport->Alpha); + viewport->LastAlpha = viewport->Alpha; + + // Optional, general purpose call to allow the backend to perform general book-keeping even if things haven't changed. + if (g.PlatformIO.Platform_UpdateWindow) + g.PlatformIO.Platform_UpdateWindow(viewport); + + if (is_new_platform_window) + { + // On startup ensure new platform window don't steal focus (give it a few frames, as nested contents may lead to viewport being created a few frames late) + if (g.FrameCount < 3) + viewport->Flags |= ImGuiViewportFlags_NoFocusOnAppearing; + + // Show window + g.PlatformIO.Platform_ShowWindow(viewport); + + // Even without focus, we assume the window becomes front-most. + // This is useful for our platform z-order heuristic when io.MouseHoveredViewport is not available. + if (viewport->LastFocusedStampCount != g.ViewportFocusedStampCount) + viewport->LastFocusedStampCount = ++g.ViewportFocusedStampCount; + } + + // Clear request flags + viewport->ClearRequestFlags(); + } +} + +// This is a default/basic function for performing the rendering/swap of multiple Platform Windows. +// Custom renderers may prefer to not call this function at all, and instead iterate the publicly exposed platform data and handle rendering/sync themselves. +// The Render/Swap functions stored in ImGuiPlatformIO are merely here to allow for this helper to exist, but you can do it yourself: +// +// ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); +// for (int i = 1; i < platform_io.Viewports.Size; i++) +// if ((platform_io.Viewports[i]->Flags & ImGuiViewportFlags_Minimized) == 0) +// MyRenderFunction(platform_io.Viewports[i], my_args); +// for (int i = 1; i < platform_io.Viewports.Size; i++) +// if ((platform_io.Viewports[i]->Flags & ImGuiViewportFlags_Minimized) == 0) +// MySwapBufferFunction(platform_io.Viewports[i], my_args); +// +void ImGui::RenderPlatformWindowsDefault(void* platform_render_arg, void* renderer_render_arg) +{ + // Skip the main viewport (index 0), which is always fully handled by the application! + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + for (int i = 1; i < platform_io.Viewports.Size; i++) + { + ImGuiViewport* viewport = platform_io.Viewports[i]; + if (viewport->Flags & ImGuiViewportFlags_IsMinimized) + continue; + if (platform_io.Platform_RenderWindow) platform_io.Platform_RenderWindow(viewport, platform_render_arg); + if (platform_io.Renderer_RenderWindow) platform_io.Renderer_RenderWindow(viewport, renderer_render_arg); + } + for (int i = 1; i < platform_io.Viewports.Size; i++) + { + ImGuiViewport* viewport = platform_io.Viewports[i]; + if (viewport->Flags & ImGuiViewportFlags_IsMinimized) + continue; + if (platform_io.Platform_SwapBuffers) platform_io.Platform_SwapBuffers(viewport, platform_render_arg); + if (platform_io.Renderer_SwapBuffers) platform_io.Renderer_SwapBuffers(viewport, renderer_render_arg); + } +} + +static int ImGui::FindPlatformMonitorForPos(const ImVec2& pos) +{ + ImGuiContext& g = *GImGui; + for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size; monitor_n++) + { + const ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[monitor_n]; + if (ImRect(monitor.MainPos, monitor.MainPos + monitor.MainSize).Contains(pos)) + return monitor_n; + } + return -1; +} + +// Search for the monitor with the largest intersection area with the given rectangle +// We generally try to avoid searching loops but the monitor count should be very small here +// FIXME-OPT: We could test the last monitor used for that viewport first, and early +static int ImGui::FindPlatformMonitorForRect(const ImRect& rect) +{ + ImGuiContext& g = *GImGui; + + const int monitor_count = g.PlatformIO.Monitors.Size; + if (monitor_count <= 1) + return monitor_count - 1; + + // Use a minimum threshold of 1.0f so a zero-sized rect won't false positive, and will still find the correct monitor given its position. + // This is necessary for tooltips which always resize down to zero at first. + const float surface_threshold = ImMax(rect.GetWidth() * rect.GetHeight() * 0.5f, 1.0f); + int best_monitor_n = -1; + float best_monitor_surface = 0.001f; + + for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size && best_monitor_surface < surface_threshold; monitor_n++) + { + const ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[monitor_n]; + const ImRect monitor_rect = ImRect(monitor.MainPos, monitor.MainPos + monitor.MainSize); + if (monitor_rect.Contains(rect)) + return monitor_n; + ImRect overlapping_rect = rect; + overlapping_rect.ClipWithFull(monitor_rect); + float overlapping_surface = overlapping_rect.GetWidth() * overlapping_rect.GetHeight(); + if (overlapping_surface < best_monitor_surface) + continue; + best_monitor_surface = overlapping_surface; + best_monitor_n = monitor_n; + } + return best_monitor_n; +} + +// Update monitor from viewport rectangle (we'll use this info to clamp windows and save windows lost in a removed monitor) +static void ImGui::UpdateViewportPlatformMonitor(ImGuiViewportP* viewport) +{ + viewport->PlatformMonitor = (short)FindPlatformMonitorForRect(viewport->GetMainRect()); +} + +// Return value is always != NULL, but don't hold on it across frames. +const ImGuiPlatformMonitor* ImGui::GetViewportPlatformMonitor(ImGuiViewport* viewport_p) +{ + ImGuiContext& g = *GImGui; + ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)viewport_p; + int monitor_idx = viewport->PlatformMonitor; + if (monitor_idx >= 0 && monitor_idx < g.PlatformIO.Monitors.Size) + return &g.PlatformIO.Monitors[monitor_idx]; + return &g.FallbackMonitor; +} + +void ImGui::DestroyPlatformWindow(ImGuiViewportP* viewport) +{ + ImGuiContext& g = *GImGui; + if (viewport->PlatformWindowCreated) + { + IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Destroy Platform Window %08X '%s'\n", viewport->ID, viewport->Window ? viewport->Window->Name : "n/a"); + if (g.PlatformIO.Renderer_DestroyWindow) + g.PlatformIO.Renderer_DestroyWindow(viewport); + if (g.PlatformIO.Platform_DestroyWindow) + g.PlatformIO.Platform_DestroyWindow(viewport); + IM_ASSERT(viewport->RendererUserData == NULL && viewport->PlatformUserData == NULL); + + // Don't clear PlatformWindowCreated for the main viewport, as we initially set that up to true in Initialize() + // The righter way may be to leave it to the backend to set this flag all-together, and made the flag public. + if (viewport->ID != IMGUI_VIEWPORT_DEFAULT_ID) + viewport->PlatformWindowCreated = false; + } + else + { + IM_ASSERT(viewport->RendererUserData == NULL && viewport->PlatformUserData == NULL && viewport->PlatformHandle == NULL); + } + viewport->RendererUserData = viewport->PlatformUserData = viewport->PlatformHandle = NULL; + viewport->ClearRequestFlags(); +} + +void ImGui::DestroyPlatformWindows() +{ + // We call the destroy window on every viewport (including the main viewport, index 0) to give a chance to the backend + // to clear any data they may have stored in e.g. PlatformUserData, RendererUserData. + // It is convenient for the platform backend code to store something in the main viewport, in order for e.g. the mouse handling + // code to operator a consistent manner. + // It is expected that the backend can handle calls to Renderer_DestroyWindow/Platform_DestroyWindow without + // crashing if it doesn't have data stored. + ImGuiContext& g = *GImGui; + for (ImGuiViewportP* viewport : g.Viewports) + DestroyPlatformWindow(viewport); +} + + +//----------------------------------------------------------------------------- +// [SECTION] DOCKING +//----------------------------------------------------------------------------- +// Docking: Internal Types +// Docking: Forward Declarations +// Docking: ImGuiDockContext +// Docking: ImGuiDockContext Docking/Undocking functions +// Docking: ImGuiDockNode +// Docking: ImGuiDockNode Tree manipulation functions +// Docking: Public Functions (SetWindowDock, DockSpace, DockSpaceOverViewport) +// Docking: Builder Functions +// Docking: Begin/End Support Functions (called from Begin/End) +// Docking: Settings +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Typical Docking call flow: (root level is generally public API): +//----------------------------------------------------------------------------- +// - NewFrame() new dear imgui frame +// | DockContextNewFrameUpdateUndocking() - process queued undocking requests +// | - DockContextProcessUndockWindow() - process one window undocking request +// | - DockContextProcessUndockNode() - process one whole node undocking request +// | DockContextNewFrameUpdateUndocking() - process queue docking requests, create floating dock nodes +// | - update g.HoveredDockNode - [debug] update node hovered by mouse +// | - DockContextProcessDock() - process one docking request +// | - DockNodeUpdate() +// | - DockNodeUpdateForRootNode() +// | - DockNodeUpdateFlagsAndCollapse() +// | - DockNodeFindInfo() +// | - destroy unused node or tab bar +// | - create dock node host window +// | - Begin() etc. +// | - DockNodeStartMouseMovingWindow() +// | - DockNodeTreeUpdatePosSize() +// | - DockNodeTreeUpdateSplitter() +// | - draw node background +// | - DockNodeUpdateTabBar() - create/update tab bar for a docking node +// | - DockNodeAddTabBar() +// | - DockNodeWindowMenuUpdate() +// | - DockNodeCalcTabBarLayout() +// | - BeginTabBarEx() +// | - TabItemEx() calls +// | - EndTabBar() +// | - BeginDockableDragDropTarget() +// | - DockNodeUpdate() - recurse into child nodes... +//----------------------------------------------------------------------------- +// - DockSpace() user submit a dockspace into a window +// | Begin(Child) - create a child window +// | DockNodeUpdate() - call main dock node update function +// | End(Child) +// | ItemSize() +//----------------------------------------------------------------------------- +// - Begin() +// | BeginDocked() +// | BeginDockableDragDropSource() +// | BeginDockableDragDropTarget() +// | - DockNodePreviewDockRender() +//----------------------------------------------------------------------------- +// - EndFrame() +// | DockContextEndFrame() +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Docking: Internal Types +//----------------------------------------------------------------------------- +// - ImGuiDockRequestType +// - ImGuiDockRequest +// - ImGuiDockPreviewData +// - ImGuiDockNodeSettings +// - ImGuiDockContext +//----------------------------------------------------------------------------- + +enum ImGuiDockRequestType +{ + ImGuiDockRequestType_None = 0, + ImGuiDockRequestType_Dock, + ImGuiDockRequestType_Undock, + ImGuiDockRequestType_Split // Split is the same as Dock but without a DockPayload +}; + +struct ImGuiDockRequest +{ + ImGuiDockRequestType Type; + ImGuiWindow* DockTargetWindow; // Destination/Target Window to dock into (may be a loose window or a DockNode, might be NULL in which case DockTargetNode cannot be NULL) + ImGuiDockNode* DockTargetNode; // Destination/Target Node to dock into + ImGuiWindow* DockPayload; // Source/Payload window to dock (may be a loose window or a DockNode), [Optional] + ImGuiDir DockSplitDir; + float DockSplitRatio; + bool DockSplitOuter; + ImGuiWindow* UndockTargetWindow; + ImGuiDockNode* UndockTargetNode; + + ImGuiDockRequest() + { + Type = ImGuiDockRequestType_None; + DockTargetWindow = DockPayload = UndockTargetWindow = NULL; + DockTargetNode = UndockTargetNode = NULL; + DockSplitDir = ImGuiDir_None; + DockSplitRatio = 0.5f; + DockSplitOuter = false; + } +}; + +struct ImGuiDockPreviewData +{ + ImGuiDockNode FutureNode; + bool IsDropAllowed; + bool IsCenterAvailable; + bool IsSidesAvailable; // Hold your breath, grammar freaks.. + bool IsSplitDirExplicit; // Set when hovered the drop rect (vs. implicit SplitDir==None when hovered the window) + ImGuiDockNode* SplitNode; + ImGuiDir SplitDir; + float SplitRatio; + ImRect DropRectsDraw[ImGuiDir_COUNT + 1]; // May be slightly different from hit-testing drop rects used in DockNodeCalcDropRects() + + ImGuiDockPreviewData() : FutureNode(0) { IsDropAllowed = IsCenterAvailable = IsSidesAvailable = IsSplitDirExplicit = false; SplitNode = NULL; SplitDir = ImGuiDir_None; SplitRatio = 0.f; for (int n = 0; n < IM_ARRAYSIZE(DropRectsDraw); n++) DropRectsDraw[n] = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); } +}; + +// Persistent Settings data, stored contiguously in SettingsNodes (sizeof() ~32 bytes) +struct ImGuiDockNodeSettings +{ + ImGuiID ID; + ImGuiID ParentNodeId; + ImGuiID ParentWindowId; + ImGuiID SelectedTabId; + signed char SplitAxis; + char Depth; + ImGuiDockNodeFlags Flags; // NB: We save individual flags one by one in ascii format (ImGuiDockNodeFlags_SavedFlagsMask_) + ImVec2ih Pos; + ImVec2ih Size; + ImVec2ih SizeRef; + ImGuiDockNodeSettings() { memset(this, 0, sizeof(*this)); SplitAxis = ImGuiAxis_None; } +}; + +//----------------------------------------------------------------------------- +// Docking: Forward Declarations +//----------------------------------------------------------------------------- + +namespace ImGui +{ + // ImGuiDockContext + static ImGuiDockNode* DockContextAddNode(ImGuiContext* ctx, ImGuiID id); + static void DockContextRemoveNode(ImGuiContext* ctx, ImGuiDockNode* node, bool merge_sibling_into_parent_node); + static void DockContextQueueNotifyRemovedNode(ImGuiContext* ctx, ImGuiDockNode* node); + static void DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req); + static void DockContextPruneUnusedSettingsNodes(ImGuiContext* ctx); + static ImGuiDockNode* DockContextBindNodeToWindow(ImGuiContext* ctx, ImGuiWindow* window); + static void DockContextBuildNodesFromSettings(ImGuiContext* ctx, ImGuiDockNodeSettings* node_settings_array, int node_settings_count); + static void DockContextBuildAddWindowsToNodes(ImGuiContext* ctx, ImGuiID root_id); // Use root_id==0 to add all + + // ImGuiDockNode + static void DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window, bool add_to_tab_bar); + static void DockNodeMoveWindows(ImGuiDockNode* dst_node, ImGuiDockNode* src_node); + static void DockNodeMoveChildNodes(ImGuiDockNode* dst_node, ImGuiDockNode* src_node); + static ImGuiWindow* DockNodeFindWindowByID(ImGuiDockNode* node, ImGuiID id); + static void DockNodeApplyPosSizeToWindows(ImGuiDockNode* node); + static void DockNodeRemoveWindow(ImGuiDockNode* node, ImGuiWindow* window, ImGuiID save_dock_id); + static void DockNodeHideHostWindow(ImGuiDockNode* node); + static void DockNodeUpdate(ImGuiDockNode* node); + static void DockNodeUpdateForRootNode(ImGuiDockNode* node); + static void DockNodeUpdateFlagsAndCollapse(ImGuiDockNode* node); + static void DockNodeUpdateHasCentralNodeChild(ImGuiDockNode* node); + static void DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_window); + static void DockNodeAddTabBar(ImGuiDockNode* node); + static void DockNodeRemoveTabBar(ImGuiDockNode* node); + static void DockNodeWindowMenuUpdate(ImGuiDockNode* node, ImGuiTabBar* tab_bar); + static void DockNodeUpdateVisibleFlag(ImGuiDockNode* node); + static void DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window); + static bool DockNodeIsDropAllowed(ImGuiWindow* host_window, ImGuiWindow* payload_window); + static void DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, ImGuiDockNode* payload_node, ImGuiDockPreviewData* preview_data, bool is_explicit_target, bool is_outer_docking); + static void DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, const ImGuiDockPreviewData* preview_data); + static void DockNodeCalcTabBarLayout(const ImGuiDockNode* node, ImRect* out_title_rect, ImRect* out_tab_bar_rect, ImVec2* out_window_menu_button_pos, ImVec2* out_close_button_pos); + static void DockNodeCalcSplitRects(ImVec2& pos_old, ImVec2& size_old, ImVec2& pos_new, ImVec2& size_new, ImGuiDir dir, ImVec2 size_new_desired); + static bool DockNodeCalcDropRectsAndTestMousePos(const ImRect& parent, ImGuiDir dir, ImRect& out_draw, bool outer_docking, ImVec2* test_mouse_pos); + static const char* DockNodeGetHostWindowTitle(ImGuiDockNode* node, char* buf, int buf_size) { ImFormatString(buf, buf_size, "##DockNode_%02X", node->ID); return buf; } + static int DockNodeGetTabOrder(ImGuiWindow* window); + + // ImGuiDockNode tree manipulations + static void DockNodeTreeSplit(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiAxis split_axis, int split_first_child, float split_ratio, ImGuiDockNode* new_node); + static void DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiDockNode* merge_lead_child); + static void DockNodeTreeUpdatePosSize(ImGuiDockNode* node, ImVec2 pos, ImVec2 size, ImGuiDockNode* only_write_to_single_node = NULL); + static void DockNodeTreeUpdateSplitter(ImGuiDockNode* node); + static ImGuiDockNode* DockNodeTreeFindVisibleNodeByPos(ImGuiDockNode* node, ImVec2 pos); + static ImGuiDockNode* DockNodeTreeFindFallbackLeafNode(ImGuiDockNode* node); + + // Settings + static void DockSettingsRenameNodeReferences(ImGuiID old_node_id, ImGuiID new_node_id); + static void DockSettingsRemoveNodeReferences(ImGuiID* node_ids, int node_ids_count); + static ImGuiDockNodeSettings* DockSettingsFindNodeSettings(ImGuiContext* ctx, ImGuiID node_id); + static void DockSettingsHandler_ClearAll(ImGuiContext*, ImGuiSettingsHandler*); + static void DockSettingsHandler_ApplyAll(ImGuiContext*, ImGuiSettingsHandler*); + static void* DockSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name); + static void DockSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line); + static void DockSettingsHandler_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf); +} + +//----------------------------------------------------------------------------- +// Docking: ImGuiDockContext +//----------------------------------------------------------------------------- +// The lifetime model is different from the one of regular windows: we always create a ImGuiDockNode for each ImGuiDockNodeSettings, +// or we always hold the entire docking node tree. Nodes are frequently hidden, e.g. if the window(s) or child nodes they host are not active. +// At boot time only, we run a simple GC to remove nodes that have no references. +// Because dock node settings (which are small, contiguous structures) are always mirrored by their corresponding dock nodes (more complete structures), +// we can also very easily recreate the nodes from scratch given the settings data (this is what DockContextRebuild() does). +// This is convenient as docking reconfiguration can be implemented by mostly poking at the simpler settings data. +//----------------------------------------------------------------------------- +// - DockContextInitialize() +// - DockContextShutdown() +// - DockContextClearNodes() +// - DockContextRebuildNodes() +// - DockContextNewFrameUpdateUndocking() +// - DockContextNewFrameUpdateDocking() +// - DockContextEndFrame() +// - DockContextFindNodeByID() +// - DockContextBindNodeToWindow() +// - DockContextGenNodeID() +// - DockContextAddNode() +// - DockContextRemoveNode() +// - ImGuiDockContextPruneNodeData +// - DockContextPruneUnusedSettingsNodes() +// - DockContextBuildNodesFromSettings() +// - DockContextBuildAddWindowsToNodes() +//----------------------------------------------------------------------------- + +void ImGui::DockContextInitialize(ImGuiContext* ctx) +{ + ImGuiContext& g = *ctx; + + // Add .ini handle for persistent docking data + ImGuiSettingsHandler ini_handler; + ini_handler.TypeName = "Docking"; + ini_handler.TypeHash = ImHashStr("Docking"); + ini_handler.ClearAllFn = DockSettingsHandler_ClearAll; + ini_handler.ReadInitFn = DockSettingsHandler_ClearAll; // Also clear on read + ini_handler.ReadOpenFn = DockSettingsHandler_ReadOpen; + ini_handler.ReadLineFn = DockSettingsHandler_ReadLine; + ini_handler.ApplyAllFn = DockSettingsHandler_ApplyAll; + ini_handler.WriteAllFn = DockSettingsHandler_WriteAll; + g.SettingsHandlers.push_back(ini_handler); + + g.DockNodeWindowMenuHandler = &DockNodeWindowMenuHandler_Default; +} + +void ImGui::DockContextShutdown(ImGuiContext* ctx) +{ + ImGuiDockContext* dc = &ctx->DockContext; + for (int n = 0; n < dc->Nodes.Data.Size; n++) + if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p) + IM_DELETE(node); +} + +void ImGui::DockContextClearNodes(ImGuiContext* ctx, ImGuiID root_id, bool clear_settings_refs) +{ + IM_UNUSED(ctx); + IM_ASSERT(ctx == GImGui); + DockBuilderRemoveNodeDockedWindows(root_id, clear_settings_refs); + DockBuilderRemoveNodeChildNodes(root_id); +} + +// [DEBUG] This function also acts as a defacto test to make sure we can rebuild from scratch without a glitch +// (Different from DockSettingsHandler_ClearAll() + DockSettingsHandler_ApplyAll() because this reuses current settings!) +void ImGui::DockContextRebuildNodes(ImGuiContext* ctx) +{ + ImGuiContext& g = *ctx; + ImGuiDockContext* dc = &ctx->DockContext; + IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextRebuildNodes\n"); + SaveIniSettingsToMemory(); + ImGuiID root_id = 0; // Rebuild all + DockContextClearNodes(ctx, root_id, false); + DockContextBuildNodesFromSettings(ctx, dc->NodesSettings.Data, dc->NodesSettings.Size); + DockContextBuildAddWindowsToNodes(ctx, root_id); +} + +// Docking context update function, called by NewFrame() +void ImGui::DockContextNewFrameUpdateUndocking(ImGuiContext* ctx) +{ + ImGuiContext& g = *ctx; + ImGuiDockContext* dc = &ctx->DockContext; + if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable)) + { + if (dc->Nodes.Data.Size > 0 || dc->Requests.Size > 0) + DockContextClearNodes(ctx, 0, true); + return; + } + + // Setting NoSplit at runtime merges all nodes + if (g.IO.ConfigDockingNoSplit) + for (int n = 0; n < dc->Nodes.Data.Size; n++) + if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p) + if (node->IsRootNode() && node->IsSplitNode()) + { + DockBuilderRemoveNodeChildNodes(node->ID); + //dc->WantFullRebuild = true; + } + + // Process full rebuild +#if 0 + if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C))) + dc->WantFullRebuild = true; +#endif + if (dc->WantFullRebuild) + { + DockContextRebuildNodes(ctx); + dc->WantFullRebuild = false; + } + + // Process Undocking requests (we need to process them _before_ the UpdateMouseMovingWindowNewFrame call in NewFrame) + for (ImGuiDockRequest& req : dc->Requests) + { + if (req.Type == ImGuiDockRequestType_Undock && req.UndockTargetWindow) + DockContextProcessUndockWindow(ctx, req.UndockTargetWindow); + else if (req.Type == ImGuiDockRequestType_Undock && req.UndockTargetNode) + DockContextProcessUndockNode(ctx, req.UndockTargetNode); + } +} + +// Docking context update function, called by NewFrame() +void ImGui::DockContextNewFrameUpdateDocking(ImGuiContext* ctx) +{ + ImGuiContext& g = *ctx; + ImGuiDockContext* dc = &ctx->DockContext; + if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable)) + return; + + // [DEBUG] Store hovered dock node. + // We could in theory use DockNodeTreeFindVisibleNodeByPos() on the root host dock node, but using ->DockNode is a good shortcut. + // Note this is mostly a debug thing and isn't actually used for docking target, because docking involve more detailed filtering. + g.DebugHoveredDockNode = NULL; + if (ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow) + { + if (hovered_window->DockNodeAsHost) + g.DebugHoveredDockNode = DockNodeTreeFindVisibleNodeByPos(hovered_window->DockNodeAsHost, g.IO.MousePos); + else if (hovered_window->RootWindow->DockNode) + g.DebugHoveredDockNode = hovered_window->RootWindow->DockNode; + } + + // Process Docking requests + for (ImGuiDockRequest& req : dc->Requests) + if (req.Type == ImGuiDockRequestType_Dock) + DockContextProcessDock(ctx, &req); + dc->Requests.resize(0); + + // Create windows for each automatic docking nodes + // We can have NULL pointers when we delete nodes, but because ID are recycled this should amortize nicely (and our node count will never be very high) + for (int n = 0; n < dc->Nodes.Data.Size; n++) + if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p) + if (node->IsFloatingNode()) + DockNodeUpdate(node); +} + +void ImGui::DockContextEndFrame(ImGuiContext* ctx) +{ + // Draw backgrounds of node missing their window + ImGuiContext& g = *ctx; + ImGuiDockContext* dc = &g.DockContext; + for (int n = 0; n < dc->Nodes.Data.Size; n++) + if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p) + if (node->LastFrameActive == g.FrameCount && node->IsVisible && node->HostWindow && node->IsLeafNode() && !node->IsBgDrawnThisFrame) + { + ImRect bg_rect(node->Pos + ImVec2(0.0f, GetFrameHeight()), node->Pos + node->Size); + ImDrawFlags bg_rounding_flags = CalcRoundingFlagsForRectInRect(bg_rect, node->HostWindow->Rect(), g.Style.DockingSeparatorSize); + node->HostWindow->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG); + node->HostWindow->DrawList->AddRectFilled(bg_rect.Min, bg_rect.Max, node->LastBgColor, node->HostWindow->WindowRounding, bg_rounding_flags); + } +} + +ImGuiDockNode* ImGui::DockContextFindNodeByID(ImGuiContext* ctx, ImGuiID id) +{ + return (ImGuiDockNode*)ctx->DockContext.Nodes.GetVoidPtr(id); +} + +ImGuiID ImGui::DockContextGenNodeID(ImGuiContext* ctx) +{ + // Generate an ID for new node (the exact ID value doesn't matter as long as it is not already used) + // FIXME-OPT FIXME-DOCK: This is suboptimal, even if the node count is small enough not to be a worry.0 + // We should poke in ctx->Nodes to find a suitable ID faster. Even more so trivial that ctx->Nodes lookup is already sorted. + ImGuiID id = 0x0001; + while (DockContextFindNodeByID(ctx, id) != NULL) + id++; + return id; +} + +static ImGuiDockNode* ImGui::DockContextAddNode(ImGuiContext* ctx, ImGuiID id) +{ + // Generate an ID for the new node (the exact ID value doesn't matter as long as it is not already used) and add the first window. + ImGuiContext& g = *ctx; + if (id == 0) + id = DockContextGenNodeID(ctx); + else + IM_ASSERT(DockContextFindNodeByID(ctx, id) == NULL); + + // We don't set node->LastFrameAlive on construction. Nodes are always created at all time to reflect .ini settings! + IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextAddNode 0x%08X\n", id); + ImGuiDockNode* node = IM_NEW(ImGuiDockNode)(id); + ctx->DockContext.Nodes.SetVoidPtr(node->ID, node); + return node; +} + +static void ImGui::DockContextRemoveNode(ImGuiContext* ctx, ImGuiDockNode* node, bool merge_sibling_into_parent_node) +{ + ImGuiContext& g = *ctx; + ImGuiDockContext* dc = &ctx->DockContext; + + IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextRemoveNode 0x%08X\n", node->ID); + IM_ASSERT(DockContextFindNodeByID(ctx, node->ID) == node); + IM_ASSERT(node->ChildNodes[0] == NULL && node->ChildNodes[1] == NULL); + IM_ASSERT(node->Windows.Size == 0); + + if (node->HostWindow) + node->HostWindow->DockNodeAsHost = NULL; + + ImGuiDockNode* parent_node = node->ParentNode; + const bool merge = (merge_sibling_into_parent_node && parent_node != NULL); + if (merge) + { + IM_ASSERT(parent_node->ChildNodes[0] == node || parent_node->ChildNodes[1] == node); + ImGuiDockNode* sibling_node = (parent_node->ChildNodes[0] == node ? parent_node->ChildNodes[1] : parent_node->ChildNodes[0]); + DockNodeTreeMerge(&g, parent_node, sibling_node); + } + else + { + for (int n = 0; parent_node && n < IM_ARRAYSIZE(parent_node->ChildNodes); n++) + if (parent_node->ChildNodes[n] == node) + node->ParentNode->ChildNodes[n] = NULL; + dc->Nodes.SetVoidPtr(node->ID, NULL); + IM_DELETE(node); + } +} + +static int IMGUI_CDECL DockNodeComparerDepthMostFirst(const void* lhs, const void* rhs) +{ + const ImGuiDockNode* a = *(const ImGuiDockNode* const*)lhs; + const ImGuiDockNode* b = *(const ImGuiDockNode* const*)rhs; + return ImGui::DockNodeGetDepth(b) - ImGui::DockNodeGetDepth(a); +} + +// Pre C++0x doesn't allow us to use a function-local type (without linkage) as template parameter, so we moved this here. +struct ImGuiDockContextPruneNodeData +{ + int CountWindows, CountChildWindows, CountChildNodes; + ImGuiID RootId; + ImGuiDockContextPruneNodeData() { CountWindows = CountChildWindows = CountChildNodes = 0; RootId = 0; } +}; + +// Garbage collect unused nodes (run once at init time) +static void ImGui::DockContextPruneUnusedSettingsNodes(ImGuiContext* ctx) +{ + ImGuiContext& g = *ctx; + ImGuiDockContext* dc = &ctx->DockContext; + IM_ASSERT(g.Windows.Size == 0); + + ImPool pool; + pool.Reserve(dc->NodesSettings.Size); + + // Count child nodes and compute RootID + for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++) + { + ImGuiDockNodeSettings* settings = &dc->NodesSettings[settings_n]; + ImGuiDockContextPruneNodeData* parent_data = settings->ParentNodeId ? pool.GetByKey(settings->ParentNodeId) : 0; + pool.GetOrAddByKey(settings->ID)->RootId = parent_data ? parent_data->RootId : settings->ID; + if (settings->ParentNodeId) + pool.GetOrAddByKey(settings->ParentNodeId)->CountChildNodes++; + } + + // Count reference to dock ids from dockspaces + // We track the 'auto-DockNode <- manual-Window <- manual-DockSpace' in order to avoid 'auto-DockNode' being ditched by DockContextPruneUnusedSettingsNodes() + for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++) + { + ImGuiDockNodeSettings* settings = &dc->NodesSettings[settings_n]; + if (settings->ParentWindowId != 0) + if (ImGuiWindowSettings* window_settings = FindWindowSettingsByID(settings->ParentWindowId)) + if (window_settings->DockId) + if (ImGuiDockContextPruneNodeData* data = pool.GetByKey(window_settings->DockId)) + data->CountChildNodes++; + } + + // Count reference to dock ids from window settings + // We guard against the possibility of an invalid .ini file (RootID may point to a missing node) + for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) + if (ImGuiID dock_id = settings->DockId) + if (ImGuiDockContextPruneNodeData* data = pool.GetByKey(dock_id)) + { + data->CountWindows++; + if (ImGuiDockContextPruneNodeData* data_root = (data->RootId == dock_id) ? data : pool.GetByKey(data->RootId)) + data_root->CountChildWindows++; + } + + // Prune + for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++) + { + ImGuiDockNodeSettings* settings = &dc->NodesSettings[settings_n]; + ImGuiDockContextPruneNodeData* data = pool.GetByKey(settings->ID); + if (data->CountWindows > 1) + continue; + ImGuiDockContextPruneNodeData* data_root = (data->RootId == settings->ID) ? data : pool.GetByKey(data->RootId); + + bool remove = false; + remove |= (data->CountWindows == 1 && settings->ParentNodeId == 0 && data->CountChildNodes == 0 && !(settings->Flags & ImGuiDockNodeFlags_CentralNode)); // Floating root node with only 1 window + remove |= (data->CountWindows == 0 && settings->ParentNodeId == 0 && data->CountChildNodes == 0); // Leaf nodes with 0 window + remove |= (data_root->CountChildWindows == 0); + if (remove) + { + IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextPruneUnusedSettingsNodes: Prune 0x%08X\n", settings->ID); + DockSettingsRemoveNodeReferences(&settings->ID, 1); + settings->ID = 0; + } + } +} + +static void ImGui::DockContextBuildNodesFromSettings(ImGuiContext* ctx, ImGuiDockNodeSettings* node_settings_array, int node_settings_count) +{ + // Build nodes + for (int node_n = 0; node_n < node_settings_count; node_n++) + { + ImGuiDockNodeSettings* settings = &node_settings_array[node_n]; + if (settings->ID == 0) + continue; + ImGuiDockNode* node = DockContextAddNode(ctx, settings->ID); + node->ParentNode = settings->ParentNodeId ? DockContextFindNodeByID(ctx, settings->ParentNodeId) : NULL; + node->Pos = ImVec2(settings->Pos.x, settings->Pos.y); + node->Size = ImVec2(settings->Size.x, settings->Size.y); + node->SizeRef = ImVec2(settings->SizeRef.x, settings->SizeRef.y); + node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_DockNode; + if (node->ParentNode && node->ParentNode->ChildNodes[0] == NULL) + node->ParentNode->ChildNodes[0] = node; + else if (node->ParentNode && node->ParentNode->ChildNodes[1] == NULL) + node->ParentNode->ChildNodes[1] = node; + node->SelectedTabId = settings->SelectedTabId; + node->SplitAxis = (ImGuiAxis)settings->SplitAxis; + node->SetLocalFlags(settings->Flags & ImGuiDockNodeFlags_SavedFlagsMask_); + + // Bind host window immediately if it already exist (in case of a rebuild) + // This is useful as the RootWindowForTitleBarHighlight links necessary to highlight the currently focused node requires node->HostWindow to be set. + char host_window_title[20]; + ImGuiDockNode* root_node = DockNodeGetRootNode(node); + node->HostWindow = FindWindowByName(DockNodeGetHostWindowTitle(root_node, host_window_title, IM_ARRAYSIZE(host_window_title))); + } +} + +void ImGui::DockContextBuildAddWindowsToNodes(ImGuiContext* ctx, ImGuiID root_id) +{ + // Rebind all windows to nodes (they can also lazily rebind but we'll have a visible glitch during the first frame) + ImGuiContext& g = *ctx; + for (ImGuiWindow* window : g.Windows) + { + if (window->DockId == 0 || window->LastFrameActive < g.FrameCount - 1) + continue; + if (window->DockNode != NULL) + continue; + + ImGuiDockNode* node = DockContextFindNodeByID(ctx, window->DockId); + IM_ASSERT(node != NULL); // This should have been called after DockContextBuildNodesFromSettings() + if (root_id == 0 || DockNodeGetRootNode(node)->ID == root_id) + DockNodeAddWindow(node, window, true); + } +} + +//----------------------------------------------------------------------------- +// Docking: ImGuiDockContext Docking/Undocking functions +//----------------------------------------------------------------------------- +// - DockContextQueueDock() +// - DockContextQueueUndockWindow() +// - DockContextQueueUndockNode() +// - DockContextQueueNotifyRemovedNode() +// - DockContextProcessDock() +// - DockContextProcessUndockWindow() +// - DockContextProcessUndockNode() +// - DockContextCalcDropPosForDocking() +//----------------------------------------------------------------------------- + +void ImGui::DockContextQueueDock(ImGuiContext* ctx, ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload, ImGuiDir split_dir, float split_ratio, bool split_outer) +{ + IM_ASSERT(target != payload); + ImGuiDockRequest req; + req.Type = ImGuiDockRequestType_Dock; + req.DockTargetWindow = target; + req.DockTargetNode = target_node; + req.DockPayload = payload; + req.DockSplitDir = split_dir; + req.DockSplitRatio = split_ratio; + req.DockSplitOuter = split_outer; + ctx->DockContext.Requests.push_back(req); +} + +void ImGui::DockContextQueueUndockWindow(ImGuiContext* ctx, ImGuiWindow* window) +{ + ImGuiDockRequest req; + req.Type = ImGuiDockRequestType_Undock; + req.UndockTargetWindow = window; + ctx->DockContext.Requests.push_back(req); +} + +void ImGui::DockContextQueueUndockNode(ImGuiContext* ctx, ImGuiDockNode* node) +{ + ImGuiDockRequest req; + req.Type = ImGuiDockRequestType_Undock; + req.UndockTargetNode = node; + ctx->DockContext.Requests.push_back(req); +} + +void ImGui::DockContextQueueNotifyRemovedNode(ImGuiContext* ctx, ImGuiDockNode* node) +{ + ImGuiDockContext* dc = &ctx->DockContext; + for (ImGuiDockRequest& req : dc->Requests) + if (req.DockTargetNode == node) + req.Type = ImGuiDockRequestType_None; +} + +void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req) +{ + IM_ASSERT((req->Type == ImGuiDockRequestType_Dock && req->DockPayload != NULL) || (req->Type == ImGuiDockRequestType_Split && req->DockPayload == NULL)); + IM_ASSERT(req->DockTargetWindow != NULL || req->DockTargetNode != NULL); + + ImGuiContext& g = *ctx; + IM_UNUSED(g); + + ImGuiWindow* payload_window = req->DockPayload; // Optional + ImGuiWindow* target_window = req->DockTargetWindow; + ImGuiDockNode* node = req->DockTargetNode; + if (payload_window) + IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessDock node 0x%08X target '%s' dock window '%s', split_dir %d\n", node ? node->ID : 0, target_window ? target_window->Name : "NULL", payload_window->Name, req->DockSplitDir); + else + IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessDock node 0x%08X, split_dir %d\n", node ? node->ID : 0, req->DockSplitDir); + + // Decide which Tab will be selected at the end of the operation + ImGuiID next_selected_id = 0; + ImGuiDockNode* payload_node = NULL; + if (payload_window) + { + payload_node = payload_window->DockNodeAsHost; + payload_window->DockNodeAsHost = NULL; // Important to clear this as the node will have its life as a child which might be merged/deleted later. + if (payload_node && payload_node->IsLeafNode()) + next_selected_id = payload_node->TabBar->NextSelectedTabId ? payload_node->TabBar->NextSelectedTabId : payload_node->TabBar->SelectedTabId; + if (payload_node == NULL) + next_selected_id = payload_window->TabId; + } + + // FIXME-DOCK: When we are trying to dock an existing single-window node into a loose window, transfer Node ID as well + // When processing an interactive split, usually LastFrameAlive will be < g.FrameCount. But DockBuilder operations can make it ==. + if (node) + IM_ASSERT(node->LastFrameAlive <= g.FrameCount); + if (node && target_window && node == target_window->DockNodeAsHost) + IM_ASSERT(node->Windows.Size > 0 || node->IsSplitNode() || node->IsCentralNode()); + + // Create new node and add existing window to it + if (node == NULL) + { + node = DockContextAddNode(ctx, 0); + node->Pos = target_window->Pos; + node->Size = target_window->Size; + if (target_window->DockNodeAsHost == NULL) + { + DockNodeAddWindow(node, target_window, true); + node->TabBar->Tabs[0].Flags &= ~ImGuiTabItemFlags_Unsorted; + target_window->DockIsActive = true; + } + } + + ImGuiDir split_dir = req->DockSplitDir; + if (split_dir != ImGuiDir_None) + { + // Split into two, one side will be our payload node unless we are dropping a loose window + const ImGuiAxis split_axis = (split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y; + const int split_inheritor_child_idx = (split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 1 : 0; // Current contents will be moved to the opposite side + const float split_ratio = req->DockSplitRatio; + DockNodeTreeSplit(ctx, node, split_axis, split_inheritor_child_idx, split_ratio, payload_node); // payload_node may be NULL here! + ImGuiDockNode* new_node = node->ChildNodes[split_inheritor_child_idx ^ 1]; + new_node->HostWindow = node->HostWindow; + node = new_node; + } + node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar); + + if (node != payload_node) + { + // Create tab bar before we call DockNodeMoveWindows (which would attempt to move the old tab-bar, which would lead us to payload tabs wrongly appearing before target tabs!) + if (node->Windows.Size > 0 && node->TabBar == NULL) + { + DockNodeAddTabBar(node); + for (int n = 0; n < node->Windows.Size; n++) + TabBarAddTab(node->TabBar, ImGuiTabItemFlags_None, node->Windows[n]); + } + + if (payload_node != NULL) + { + // Transfer full payload node (with 1+ child windows or child nodes) + if (payload_node->IsSplitNode()) + { + if (node->Windows.Size > 0) + { + // We can dock a split payload into a node that already has windows _only_ if our payload is a node tree with a single visible node. + // In this situation, we move the windows of the target node into the currently visible node of the payload. + // This allows us to preserve some of the underlying dock tree settings nicely. + IM_ASSERT(payload_node->OnlyNodeWithWindows != NULL); // The docking should have been blocked by DockNodePreviewDockSetup() early on and never submitted. + ImGuiDockNode* visible_node = payload_node->OnlyNodeWithWindows; + if (visible_node->TabBar) + IM_ASSERT(visible_node->TabBar->Tabs.Size > 0); + DockNodeMoveWindows(node, visible_node); + DockNodeMoveWindows(visible_node, node); + DockSettingsRenameNodeReferences(node->ID, visible_node->ID); + } + if (node->IsCentralNode()) + { + // Central node property needs to be moved to a leaf node, pick the last focused one. + // FIXME-DOCK: If we had to transfer other flags here, what would the policy be? + ImGuiDockNode* last_focused_node = DockContextFindNodeByID(ctx, payload_node->LastFocusedNodeId); + IM_ASSERT(last_focused_node != NULL); + ImGuiDockNode* last_focused_root_node = DockNodeGetRootNode(last_focused_node); + IM_ASSERT(last_focused_root_node == DockNodeGetRootNode(payload_node)); + last_focused_node->SetLocalFlags(last_focused_node->LocalFlags | ImGuiDockNodeFlags_CentralNode); + node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_CentralNode); + last_focused_root_node->CentralNode = last_focused_node; + } + + IM_ASSERT(node->Windows.Size == 0); + DockNodeMoveChildNodes(node, payload_node); + } + else + { + const ImGuiID payload_dock_id = payload_node->ID; + DockNodeMoveWindows(node, payload_node); + DockSettingsRenameNodeReferences(payload_dock_id, node->ID); + } + DockContextRemoveNode(ctx, payload_node, true); + } + else if (payload_window) + { + // Transfer single window + const ImGuiID payload_dock_id = payload_window->DockId; + node->VisibleWindow = payload_window; + DockNodeAddWindow(node, payload_window, true); + if (payload_dock_id != 0) + DockSettingsRenameNodeReferences(payload_dock_id, node->ID); + } + } + else + { + // When docking a floating single window node we want to reevaluate auto-hiding of the tab bar + node->WantHiddenTabBarUpdate = true; + } + + // Update selection immediately + if (ImGuiTabBar* tab_bar = node->TabBar) + tab_bar->NextSelectedTabId = next_selected_id; + MarkIniSettingsDirty(); +} + +// Problem: +// Undocking a large (~full screen) window would leave it so large that the bottom right sizing corner would more +// than likely be off the screen and the window would be hard to resize to fit on screen. This can be particularly problematic +// with 'ConfigWindowsMoveFromTitleBarOnly=true' and/or with 'ConfigWindowsResizeFromEdges=false' as well (the later can be +// due to missing ImGuiBackendFlags_HasMouseCursors backend flag). +// Solution: +// When undocking a window we currently force its maximum size to 90% of the host viewport or monitor. +// Reevaluate this when we implement preserving docked/undocked size ("docking_wip/undocked_size" branch). +static ImVec2 FixLargeWindowsWhenUndocking(const ImVec2& size, ImGuiViewport* ref_viewport) +{ + if (ref_viewport == NULL) + return size; + + ImGuiContext& g = *GImGui; + ImVec2 max_size = ImTrunc(ref_viewport->WorkSize * 0.90f); + if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) + { + const ImGuiPlatformMonitor* monitor = ImGui::GetViewportPlatformMonitor(ref_viewport); + max_size = ImTrunc(monitor->WorkSize * 0.90f); + } + return ImMin(size, max_size); +} + +void ImGui::DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* window, bool clear_persistent_docking_ref) +{ + ImGuiContext& g = *ctx; + IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessUndockWindow window '%s', clear_persistent_docking_ref = %d\n", window->Name, clear_persistent_docking_ref); + if (window->DockNode) + DockNodeRemoveWindow(window->DockNode, window, clear_persistent_docking_ref ? 0 : window->DockId); + else + window->DockId = 0; + window->Collapsed = false; + window->DockIsActive = false; + window->DockNodeIsVisible = window->DockTabIsVisible = false; + window->Size = window->SizeFull = FixLargeWindowsWhenUndocking(window->SizeFull, window->Viewport); + + MarkIniSettingsDirty(); +} + +void ImGui::DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node) +{ + ImGuiContext& g = *ctx; + IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessUndockNode node %08X\n", node->ID); + IM_ASSERT(node->IsLeafNode()); + IM_ASSERT(node->Windows.Size >= 1); + + if (node->IsRootNode() || node->IsCentralNode()) + { + // In the case of a root node or central node, the node will have to stay in place. Create a new node to receive the payload. + ImGuiDockNode* new_node = DockContextAddNode(ctx, 0); + new_node->Pos = node->Pos; + new_node->Size = node->Size; + new_node->SizeRef = node->SizeRef; + DockNodeMoveWindows(new_node, node); + DockSettingsRenameNodeReferences(node->ID, new_node->ID); + node = new_node; + } + else + { + // Otherwise extract our node and merge our sibling back into the parent node. + IM_ASSERT(node->ParentNode->ChildNodes[0] == node || node->ParentNode->ChildNodes[1] == node); + int index_in_parent = (node->ParentNode->ChildNodes[0] == node) ? 0 : 1; + node->ParentNode->ChildNodes[index_in_parent] = NULL; + DockNodeTreeMerge(ctx, node->ParentNode, node->ParentNode->ChildNodes[index_in_parent ^ 1]); + node->ParentNode->AuthorityForViewport = ImGuiDataAuthority_Window; // The node that stays in place keeps the viewport, so our newly dragged out node will create a new viewport + node->ParentNode = NULL; + } + for (ImGuiWindow* window : node->Windows) + { + window->Flags &= ~ImGuiWindowFlags_ChildWindow; + if (window->ParentWindow) + window->ParentWindow->DC.ChildWindows.find_erase(window); + UpdateWindowParentAndRootLinks(window, window->Flags, NULL); + } + node->AuthorityForPos = node->AuthorityForSize = ImGuiDataAuthority_DockNode; + node->Size = FixLargeWindowsWhenUndocking(node->Size, node->Windows[0]->Viewport); + node->WantMouseMove = true; + MarkIniSettingsDirty(); +} + +// This is mostly used for automation. +bool ImGui::DockContextCalcDropPosForDocking(ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload_window, ImGuiDockNode* payload_node, ImGuiDir split_dir, bool split_outer, ImVec2* out_pos) +{ + if (target != NULL && target_node == NULL) + target_node = target->DockNode; + + // In DockNodePreviewDockSetup() for a root central node instead of showing both "inner" and "outer" drop rects + // (which would be functionally identical) we only show the outer one. Reflect this here. + if (target_node && target_node->ParentNode == NULL && target_node->IsCentralNode() && split_dir != ImGuiDir_None) + split_outer = true; + ImGuiDockPreviewData split_data; + DockNodePreviewDockSetup(target, target_node, payload_window, payload_node, &split_data, false, split_outer); + if (split_data.DropRectsDraw[split_dir+1].IsInverted()) + return false; + *out_pos = split_data.DropRectsDraw[split_dir+1].GetCenter(); + return true; +} + +//----------------------------------------------------------------------------- +// Docking: ImGuiDockNode +//----------------------------------------------------------------------------- +// - DockNodeGetTabOrder() +// - DockNodeAddWindow() +// - DockNodeRemoveWindow() +// - DockNodeMoveChildNodes() +// - DockNodeMoveWindows() +// - DockNodeApplyPosSizeToWindows() +// - DockNodeHideHostWindow() +// - ImGuiDockNodeFindInfoResults +// - DockNodeFindInfo() +// - DockNodeFindWindowByID() +// - DockNodeUpdateFlagsAndCollapse() +// - DockNodeUpdateHasCentralNodeFlag() +// - DockNodeUpdateVisibleFlag() +// - DockNodeStartMouseMovingWindow() +// - DockNodeUpdate() +// - DockNodeUpdateWindowMenu() +// - DockNodeBeginAmendTabBar() +// - DockNodeEndAmendTabBar() +// - DockNodeUpdateTabBar() +// - DockNodeAddTabBar() +// - DockNodeRemoveTabBar() +// - DockNodeIsDropAllowedOne() +// - DockNodeIsDropAllowed() +// - DockNodeCalcTabBarLayout() +// - DockNodeCalcSplitRects() +// - DockNodeCalcDropRectsAndTestMousePos() +// - DockNodePreviewDockSetup() +// - DockNodePreviewDockRender() +//----------------------------------------------------------------------------- + +ImGuiDockNode::ImGuiDockNode(ImGuiID id) +{ + ID = id; + SharedFlags = LocalFlags = LocalFlagsInWindows = MergedFlags = ImGuiDockNodeFlags_None; + ParentNode = ChildNodes[0] = ChildNodes[1] = NULL; + TabBar = NULL; + SplitAxis = ImGuiAxis_None; + + State = ImGuiDockNodeState_Unknown; + LastBgColor = IM_COL32_WHITE; + HostWindow = VisibleWindow = NULL; + CentralNode = OnlyNodeWithWindows = NULL; + CountNodeWithWindows = 0; + LastFrameAlive = LastFrameActive = LastFrameFocused = -1; + LastFocusedNodeId = 0; + SelectedTabId = 0; + WantCloseTabId = 0; + RefViewportId = 0; + AuthorityForPos = AuthorityForSize = ImGuiDataAuthority_DockNode; + AuthorityForViewport = ImGuiDataAuthority_Auto; + IsVisible = true; + IsFocused = HasCloseButton = HasWindowMenuButton = HasCentralNodeChild = false; + IsBgDrawnThisFrame = false; + WantCloseAll = WantLockSizeOnce = WantMouseMove = WantHiddenTabBarUpdate = WantHiddenTabBarToggle = false; +} + +ImGuiDockNode::~ImGuiDockNode() +{ + IM_DELETE(TabBar); + TabBar = NULL; + ChildNodes[0] = ChildNodes[1] = NULL; +} + +int ImGui::DockNodeGetTabOrder(ImGuiWindow* window) +{ + ImGuiTabBar* tab_bar = window->DockNode->TabBar; + if (tab_bar == NULL) + return -1; + ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, window->TabId); + return tab ? TabBarGetTabOrder(tab_bar, tab) : -1; +} + +static void DockNodeHideWindowDuringHostWindowCreation(ImGuiWindow* window) +{ + window->Hidden = true; + window->HiddenFramesCanSkipItems = window->Active ? 1 : 2; +} + +static void ImGui::DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window, bool add_to_tab_bar) +{ + ImGuiContext& g = *GImGui; (void)g; + if (window->DockNode) + { + // Can overwrite an existing window->DockNode (e.g. pointing to a disabled DockSpace node) + IM_ASSERT(window->DockNode->ID != node->ID); + DockNodeRemoveWindow(window->DockNode, window, 0); + } + IM_ASSERT(window->DockNode == NULL || window->DockNodeAsHost == NULL); + IMGUI_DEBUG_LOG_DOCKING("[docking] DockNodeAddWindow node 0x%08X window '%s'\n", node->ID, window->Name); + + // If more than 2 windows appeared on the same frame leading to the creation of a new hosting window, + // we'll hide windows until the host window is ready. Hide the 1st window after its been output (so it is not visible for one frame). + // We will call DockNodeHideWindowDuringHostWindowCreation() on ourselves in Begin() + if (node->HostWindow == NULL && node->Windows.Size == 1 && node->Windows[0]->WasActive == false) + DockNodeHideWindowDuringHostWindowCreation(node->Windows[0]); + + node->Windows.push_back(window); + node->WantHiddenTabBarUpdate = true; + window->DockNode = node; + window->DockId = node->ID; + window->DockIsActive = (node->Windows.Size > 1); + window->DockTabWantClose = false; + + // When reactivating a node with one or two loose window, the window pos/size/viewport are authoritative over the node storage. + // In particular it is important we init the viewport from the first window so we don't create two viewports and drop one. + if (node->HostWindow == NULL && node->IsFloatingNode()) + { + if (node->AuthorityForPos == ImGuiDataAuthority_Auto) + node->AuthorityForPos = ImGuiDataAuthority_Window; + if (node->AuthorityForSize == ImGuiDataAuthority_Auto) + node->AuthorityForSize = ImGuiDataAuthority_Window; + if (node->AuthorityForViewport == ImGuiDataAuthority_Auto) + node->AuthorityForViewport = ImGuiDataAuthority_Window; + } + + // Add to tab bar if requested + if (add_to_tab_bar) + { + if (node->TabBar == NULL) + { + DockNodeAddTabBar(node); + node->TabBar->SelectedTabId = node->TabBar->NextSelectedTabId = node->SelectedTabId; + + // Add existing windows + for (int n = 0; n < node->Windows.Size - 1; n++) + TabBarAddTab(node->TabBar, ImGuiTabItemFlags_None, node->Windows[n]); + } + TabBarAddTab(node->TabBar, ImGuiTabItemFlags_Unsorted, window); + } + + DockNodeUpdateVisibleFlag(node); + + // Update this without waiting for the next time we Begin() in the window, so our host window will have the proper title bar color on its first frame. + if (node->HostWindow) + UpdateWindowParentAndRootLinks(window, window->Flags | ImGuiWindowFlags_ChildWindow, node->HostWindow); +} + +static void ImGui::DockNodeRemoveWindow(ImGuiDockNode* node, ImGuiWindow* window, ImGuiID save_dock_id) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(window->DockNode == node); + //IM_ASSERT(window->RootWindowDockTree == node->HostWindow); + //IM_ASSERT(window->LastFrameActive < g.FrameCount); // We may call this from Begin() + IM_ASSERT(save_dock_id == 0 || save_dock_id == node->ID); + IMGUI_DEBUG_LOG_DOCKING("[docking] DockNodeRemoveWindow node 0x%08X window '%s'\n", node->ID, window->Name); + + window->DockNode = NULL; + window->DockIsActive = window->DockTabWantClose = false; + window->DockId = save_dock_id; + window->Flags &= ~ImGuiWindowFlags_ChildWindow; + if (window->ParentWindow) + window->ParentWindow->DC.ChildWindows.find_erase(window); + UpdateWindowParentAndRootLinks(window, window->Flags, NULL); // Update immediately + + if (node->HostWindow && node->HostWindow->ViewportOwned) + { + // When undocking from a user interaction this will always run in NewFrame() and have not much effect. + // But mid-frame, if we clear viewport we need to mark window as hidden as well. + window->Viewport = NULL; + window->ViewportId = 0; + window->ViewportOwned = false; + window->Hidden = true; + } + + // Remove window + bool erased = false; + for (int n = 0; n < node->Windows.Size; n++) + if (node->Windows[n] == window) + { + node->Windows.erase(node->Windows.Data + n); + erased = true; + break; + } + if (!erased) + IM_ASSERT(erased); + if (node->VisibleWindow == window) + node->VisibleWindow = NULL; + + // Remove tab and possibly tab bar + node->WantHiddenTabBarUpdate = true; + if (node->TabBar) + { + TabBarRemoveTab(node->TabBar, window->TabId); + const int tab_count_threshold_for_tab_bar = node->IsCentralNode() ? 1 : 2; + if (node->Windows.Size < tab_count_threshold_for_tab_bar) + DockNodeRemoveTabBar(node); + } + + if (node->Windows.Size == 0 && !node->IsCentralNode() && !node->IsDockSpace() && window->DockId != node->ID) + { + // Automatic dock node delete themselves if they are not holding at least one tab + DockContextRemoveNode(&g, node, true); + return; + } + + if (node->Windows.Size == 1 && !node->IsCentralNode() && node->HostWindow) + { + ImGuiWindow* remaining_window = node->Windows[0]; + // Note: we used to transport viewport ownership here. + remaining_window->Collapsed = node->HostWindow->Collapsed; + } + + // Update visibility immediately is required so the DockNodeUpdateRemoveInactiveChilds() processing can reflect changes up the tree + DockNodeUpdateVisibleFlag(node); +} + +static void ImGui::DockNodeMoveChildNodes(ImGuiDockNode* dst_node, ImGuiDockNode* src_node) +{ + IM_ASSERT(dst_node->Windows.Size == 0); + dst_node->ChildNodes[0] = src_node->ChildNodes[0]; + dst_node->ChildNodes[1] = src_node->ChildNodes[1]; + if (dst_node->ChildNodes[0]) + dst_node->ChildNodes[0]->ParentNode = dst_node; + if (dst_node->ChildNodes[1]) + dst_node->ChildNodes[1]->ParentNode = dst_node; + dst_node->SplitAxis = src_node->SplitAxis; + dst_node->SizeRef = src_node->SizeRef; + src_node->ChildNodes[0] = src_node->ChildNodes[1] = NULL; +} + +static void ImGui::DockNodeMoveWindows(ImGuiDockNode* dst_node, ImGuiDockNode* src_node) +{ + // Insert tabs in the same orders as currently ordered (node->Windows isn't ordered) + IM_ASSERT(src_node && dst_node && dst_node != src_node); + ImGuiTabBar* src_tab_bar = src_node->TabBar; + if (src_tab_bar != NULL) + IM_ASSERT(src_node->Windows.Size <= src_node->TabBar->Tabs.Size); + + // If the dst_node is empty we can just move the entire tab bar (to preserve selection, scrolling, etc.) + bool move_tab_bar = (src_tab_bar != NULL) && (dst_node->TabBar == NULL); + if (move_tab_bar) + { + dst_node->TabBar = src_node->TabBar; + src_node->TabBar = NULL; + } + + // Tab order is not important here, it is preserved by sorting in DockNodeUpdateTabBar(). + for (ImGuiWindow* window : src_node->Windows) + { + window->DockNode = NULL; + window->DockIsActive = false; + DockNodeAddWindow(dst_node, window, !move_tab_bar); + } + src_node->Windows.clear(); + + if (!move_tab_bar && src_node->TabBar) + { + if (dst_node->TabBar) + dst_node->TabBar->SelectedTabId = src_node->TabBar->SelectedTabId; + DockNodeRemoveTabBar(src_node); + } +} + +static void ImGui::DockNodeApplyPosSizeToWindows(ImGuiDockNode* node) +{ + for (ImGuiWindow* window : node->Windows) + { + SetWindowPos(window, node->Pos, ImGuiCond_Always); // We don't assign directly to Pos because it can break the calculation of SizeContents on next frame + SetWindowSize(window, node->Size, ImGuiCond_Always); + } +} + +static void ImGui::DockNodeHideHostWindow(ImGuiDockNode* node) +{ + if (node->HostWindow) + { + if (node->HostWindow->DockNodeAsHost == node) + node->HostWindow->DockNodeAsHost = NULL; + node->HostWindow = NULL; + } + + if (node->Windows.Size == 1) + { + node->VisibleWindow = node->Windows[0]; + node->Windows[0]->DockIsActive = false; + } + + if (node->TabBar) + DockNodeRemoveTabBar(node); +} + +// Search function called once by root node in DockNodeUpdate() +struct ImGuiDockNodeTreeInfo +{ + ImGuiDockNode* CentralNode; + ImGuiDockNode* FirstNodeWithWindows; + int CountNodesWithWindows; + //ImGuiWindowClass WindowClassForMerges; + + ImGuiDockNodeTreeInfo() { memset(this, 0, sizeof(*this)); } +}; + +static void DockNodeFindInfo(ImGuiDockNode* node, ImGuiDockNodeTreeInfo* info) +{ + if (node->Windows.Size > 0) + { + if (info->FirstNodeWithWindows == NULL) + info->FirstNodeWithWindows = node; + info->CountNodesWithWindows++; + } + if (node->IsCentralNode()) + { + IM_ASSERT(info->CentralNode == NULL); // Should be only one + IM_ASSERT(node->IsLeafNode() && "If you get this assert: please submit .ini file + repro of actions leading to this."); + info->CentralNode = node; + } + if (info->CountNodesWithWindows > 1 && info->CentralNode != NULL) + return; + if (node->ChildNodes[0]) + DockNodeFindInfo(node->ChildNodes[0], info); + if (node->ChildNodes[1]) + DockNodeFindInfo(node->ChildNodes[1], info); +} + +static ImGuiWindow* ImGui::DockNodeFindWindowByID(ImGuiDockNode* node, ImGuiID id) +{ + IM_ASSERT(id != 0); + for (ImGuiWindow* window : node->Windows) + if (window->ID == id) + return window; + return NULL; +} + +// - Remove inactive windows/nodes. +// - Update visibility flag. +static void ImGui::DockNodeUpdateFlagsAndCollapse(ImGuiDockNode* node) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(node->ParentNode == NULL || node->ParentNode->ChildNodes[0] == node || node->ParentNode->ChildNodes[1] == node); + + // Inherit most flags + if (node->ParentNode) + node->SharedFlags = node->ParentNode->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_; + + // Recurse into children + // There is the possibility that one of our child becoming empty will delete itself and moving its sibling contents into 'node'. + // If 'node->ChildNode[0]' delete itself, then 'node->ChildNode[1]->Windows' will be moved into 'node' + // If 'node->ChildNode[1]' delete itself, then 'node->ChildNode[0]->Windows' will be moved into 'node' and the "remove inactive windows" loop will have run twice on those windows (harmless) + node->HasCentralNodeChild = false; + if (node->ChildNodes[0]) + DockNodeUpdateFlagsAndCollapse(node->ChildNodes[0]); + if (node->ChildNodes[1]) + DockNodeUpdateFlagsAndCollapse(node->ChildNodes[1]); + + // Remove inactive windows, collapse nodes + // Merge node flags overrides stored in windows + node->LocalFlagsInWindows = ImGuiDockNodeFlags_None; + for (int window_n = 0; window_n < node->Windows.Size; window_n++) + { + ImGuiWindow* window = node->Windows[window_n]; + IM_ASSERT(window->DockNode == node); + + bool node_was_active = (node->LastFrameActive + 1 == g.FrameCount); + bool remove = false; + remove |= node_was_active && (window->LastFrameActive + 1 < g.FrameCount); + remove |= node_was_active && (node->WantCloseAll || node->WantCloseTabId == window->TabId) && window->HasCloseButton && !(window->Flags & ImGuiWindowFlags_UnsavedDocument); // Submit all _expected_ closure from last frame + remove |= (window->DockTabWantClose); + if (remove) + { + window->DockTabWantClose = false; + if (node->Windows.Size == 1 && !node->IsCentralNode()) + { + DockNodeHideHostWindow(node); + node->State = ImGuiDockNodeState_HostWindowHiddenBecauseSingleWindow; + DockNodeRemoveWindow(node, window, node->ID); // Will delete the node so it'll be invalid on return + return; + } + DockNodeRemoveWindow(node, window, node->ID); + window_n--; + continue; + } + + // FIXME-DOCKING: Missing policies for conflict resolution, hence the "Experimental" tag on this. + //node->LocalFlagsInWindow &= ~window->WindowClass.DockNodeFlagsOverrideClear; + node->LocalFlagsInWindows |= window->WindowClass.DockNodeFlagsOverrideSet; + } + node->UpdateMergedFlags(); + + // Auto-hide tab bar option + ImGuiDockNodeFlags node_flags = node->MergedFlags; + if (node->WantHiddenTabBarUpdate && node->Windows.Size == 1 && (node_flags & ImGuiDockNodeFlags_AutoHideTabBar) && !node->IsHiddenTabBar()) + node->WantHiddenTabBarToggle = true; + node->WantHiddenTabBarUpdate = false; + + // Cancel toggling if we know our tab bar is enforced to be hidden at all times + if (node->WantHiddenTabBarToggle && node->VisibleWindow && (node->VisibleWindow->WindowClass.DockNodeFlagsOverrideSet & ImGuiDockNodeFlags_HiddenTabBar)) + node->WantHiddenTabBarToggle = false; + + // Apply toggles at a single point of the frame (here!) + if (node->Windows.Size > 1) + node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar); + else if (node->WantHiddenTabBarToggle) + node->SetLocalFlags(node->LocalFlags ^ ImGuiDockNodeFlags_HiddenTabBar); + node->WantHiddenTabBarToggle = false; + + DockNodeUpdateVisibleFlag(node); +} + +// This is rarely called as DockNodeUpdateForRootNode() generally does it most frames. +static void ImGui::DockNodeUpdateHasCentralNodeChild(ImGuiDockNode* node) +{ + node->HasCentralNodeChild = false; + if (node->ChildNodes[0]) + DockNodeUpdateHasCentralNodeChild(node->ChildNodes[0]); + if (node->ChildNodes[1]) + DockNodeUpdateHasCentralNodeChild(node->ChildNodes[1]); + if (node->IsRootNode()) + { + ImGuiDockNode* mark_node = node->CentralNode; + while (mark_node) + { + mark_node->HasCentralNodeChild = true; + mark_node = mark_node->ParentNode; + } + } +} + +static void ImGui::DockNodeUpdateVisibleFlag(ImGuiDockNode* node) +{ + // Update visibility flag + bool is_visible = (node->ParentNode == NULL) ? node->IsDockSpace() : node->IsCentralNode(); + is_visible |= (node->Windows.Size > 0); + is_visible |= (node->ChildNodes[0] && node->ChildNodes[0]->IsVisible); + is_visible |= (node->ChildNodes[1] && node->ChildNodes[1]->IsVisible); + node->IsVisible = is_visible; +} + +static void ImGui::DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(node->WantMouseMove == true); + StartMouseMovingWindow(window); + g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - node->Pos; + g.MovingWindow = window; // If we are docked into a non moveable root window, StartMouseMovingWindow() won't set g.MovingWindow. Override that decision. + node->WantMouseMove = false; +} + +// Update CentralNode, OnlyNodeWithWindows, LastFocusedNodeID. Copy window class. +static void ImGui::DockNodeUpdateForRootNode(ImGuiDockNode* node) +{ + DockNodeUpdateFlagsAndCollapse(node); + + // - Setup central node pointers + // - Find if there's only a single visible window in the hierarchy (in which case we need to display a regular title bar -> FIXME-DOCK: that last part is not done yet!) + // Cannot merge this with DockNodeUpdateFlagsAndCollapse() because FirstNodeWithWindows is found after window removal and child collapsing + ImGuiDockNodeTreeInfo info; + DockNodeFindInfo(node, &info); + node->CentralNode = info.CentralNode; + node->OnlyNodeWithWindows = (info.CountNodesWithWindows == 1) ? info.FirstNodeWithWindows : NULL; + node->CountNodeWithWindows = info.CountNodesWithWindows; + if (node->LastFocusedNodeId == 0 && info.FirstNodeWithWindows != NULL) + node->LastFocusedNodeId = info.FirstNodeWithWindows->ID; + + // Copy the window class from of our first window so it can be used for proper dock filtering. + // When node has mixed windows, prioritize the class with the most constraint (DockingAllowUnclassed = false) as the reference to copy. + // FIXME-DOCK: We don't recurse properly, this code could be reworked to work from DockNodeUpdateScanRec. + if (ImGuiDockNode* first_node_with_windows = info.FirstNodeWithWindows) + { + node->WindowClass = first_node_with_windows->Windows[0]->WindowClass; + for (int n = 1; n < first_node_with_windows->Windows.Size; n++) + if (first_node_with_windows->Windows[n]->WindowClass.DockingAllowUnclassed == false) + { + node->WindowClass = first_node_with_windows->Windows[n]->WindowClass; + break; + } + } + + ImGuiDockNode* mark_node = node->CentralNode; + while (mark_node) + { + mark_node->HasCentralNodeChild = true; + mark_node = mark_node->ParentNode; + } +} + +static void DockNodeSetupHostWindow(ImGuiDockNode* node, ImGuiWindow* host_window) +{ + // Remove ourselves from any previous different host window + // This can happen if a user mistakenly does (see #4295 for details): + // - N+0: DockBuilderAddNode(id, 0) // missing ImGuiDockNodeFlags_DockSpace + // - N+1: NewFrame() // will create floating host window for that node + // - N+1: DockSpace(id) // requalify node as dockspace, moving host window + if (node->HostWindow && node->HostWindow != host_window && node->HostWindow->DockNodeAsHost == node) + node->HostWindow->DockNodeAsHost = NULL; + + host_window->DockNodeAsHost = node; + node->HostWindow = host_window; +} + +static void ImGui::DockNodeUpdate(ImGuiDockNode* node) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(node->LastFrameActive != g.FrameCount); + node->LastFrameAlive = g.FrameCount; + node->IsBgDrawnThisFrame = false; + + node->CentralNode = node->OnlyNodeWithWindows = NULL; + if (node->IsRootNode()) + DockNodeUpdateForRootNode(node); + + // Remove tab bar if not needed + if (node->TabBar && node->IsNoTabBar()) + DockNodeRemoveTabBar(node); + + // Early out for hidden root dock nodes (when all DockId references are in inactive windows, or there is only 1 floating window holding on the DockId) + bool want_to_hide_host_window = false; + if (node->IsFloatingNode()) + { + if (node->Windows.Size <= 1 && node->IsLeafNode()) + if (!g.IO.ConfigDockingAlwaysTabBar && (node->Windows.Size == 0 || !node->Windows[0]->WindowClass.DockingAlwaysTabBar)) + want_to_hide_host_window = true; + if (node->CountNodeWithWindows == 0) + want_to_hide_host_window = true; + } + if (want_to_hide_host_window) + { + if (node->Windows.Size == 1) + { + // Floating window pos/size is authoritative + ImGuiWindow* single_window = node->Windows[0]; + node->Pos = single_window->Pos; + node->Size = single_window->SizeFull; + node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Window; + + // Transfer focus immediately so when we revert to a regular window it is immediately selected + if (node->HostWindow && g.NavWindow == node->HostWindow) + FocusWindow(single_window); + if (node->HostWindow) + { + IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Node %08X transfer Viewport %08X->%08X to Window '%s'\n", node->ID, node->HostWindow->Viewport->ID, single_window->ID, single_window->Name); + single_window->Viewport = node->HostWindow->Viewport; + single_window->ViewportId = node->HostWindow->ViewportId; + if (node->HostWindow->ViewportOwned) + { + single_window->Viewport->ID = single_window->ID; + single_window->Viewport->Window = single_window; + single_window->ViewportOwned = true; + } + } + node->RefViewportId = single_window->ViewportId; + } + + DockNodeHideHostWindow(node); + node->State = ImGuiDockNodeState_HostWindowHiddenBecauseSingleWindow; + node->WantCloseAll = false; + node->WantCloseTabId = 0; + node->HasCloseButton = node->HasWindowMenuButton = false; + node->LastFrameActive = g.FrameCount; + + if (node->WantMouseMove && node->Windows.Size == 1) + DockNodeStartMouseMovingWindow(node, node->Windows[0]); + return; + } + + // In some circumstance we will defer creating the host window (so everything will be kept hidden), + // while the expected visible window is resizing itself. + // This is important for first-time (no ini settings restored) single window when io.ConfigDockingAlwaysTabBar is enabled, + // otherwise the node ends up using the minimum window size. Effectively those windows will take an extra frame to show up: + // N+0: Begin(): window created (with no known size), node is created + // N+1: DockNodeUpdate(): node skip creating host window / Begin(): window size applied, not visible + // N+2: DockNodeUpdate(): node can create host window / Begin(): window becomes visible + // We could remove this frame if we could reliably calculate the expected window size during node update, before the Begin() code. + // It would require a generalization of CalcWindowExpectedSize(), probably extracting code away from Begin(). + // In reality it isn't very important as user quickly ends up with size data in .ini file. + if (node->IsVisible && node->HostWindow == NULL && node->IsFloatingNode() && node->IsLeafNode()) + { + IM_ASSERT(node->Windows.Size > 0); + ImGuiWindow* ref_window = NULL; + if (node->SelectedTabId != 0) // Note that we prune single-window-node settings on .ini loading, so this is generally 0 for them! + ref_window = DockNodeFindWindowByID(node, node->SelectedTabId); + if (ref_window == NULL) + ref_window = node->Windows[0]; + if (ref_window->AutoFitFramesX > 0 || ref_window->AutoFitFramesY > 0) + { + node->State = ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing; + return; + } + } + + const ImGuiDockNodeFlags node_flags = node->MergedFlags; + + // Decide if the node will have a close button and a window menu button + node->HasWindowMenuButton = (node->Windows.Size > 0) && (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0; + node->HasCloseButton = false; + for (ImGuiWindow* window : node->Windows) + { + // FIXME-DOCK: Setting DockIsActive here means that for single active window in a leaf node, DockIsActive will be cleared until the next Begin() call. + node->HasCloseButton |= window->HasCloseButton; + window->DockIsActive = (node->Windows.Size > 1); + } + if (node_flags & ImGuiDockNodeFlags_NoCloseButton) + node->HasCloseButton = false; + + // Bind or create host window + ImGuiWindow* host_window = NULL; + bool beginned_into_host_window = false; + if (node->IsDockSpace()) + { + // [Explicit root dockspace node] + IM_ASSERT(node->HostWindow); + host_window = node->HostWindow; + } + else + { + // [Automatic root or child nodes] + if (node->IsRootNode() && node->IsVisible) + { + ImGuiWindow* ref_window = (node->Windows.Size > 0) ? node->Windows[0] : NULL; + + // Sync Pos + if (node->AuthorityForPos == ImGuiDataAuthority_Window && ref_window) + SetNextWindowPos(ref_window->Pos); + else if (node->AuthorityForPos == ImGuiDataAuthority_DockNode) + SetNextWindowPos(node->Pos); + + // Sync Size + if (node->AuthorityForSize == ImGuiDataAuthority_Window && ref_window) + SetNextWindowSize(ref_window->SizeFull); + else if (node->AuthorityForSize == ImGuiDataAuthority_DockNode) + SetNextWindowSize(node->Size); + + // Sync Collapsed + if (node->AuthorityForSize == ImGuiDataAuthority_Window && ref_window) + SetNextWindowCollapsed(ref_window->Collapsed); + + // Sync Viewport + if (node->AuthorityForViewport == ImGuiDataAuthority_Window && ref_window) + SetNextWindowViewport(ref_window->ViewportId); + else if (node->AuthorityForViewport == ImGuiDataAuthority_Window && node->RefViewportId != 0) + SetNextWindowViewport(node->RefViewportId); + + SetNextWindowClass(&node->WindowClass); + + // Begin into the host window + char window_label[20]; + DockNodeGetHostWindowTitle(node, window_label, IM_ARRAYSIZE(window_label)); + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_DockNodeHost; + window_flags |= ImGuiWindowFlags_NoFocusOnAppearing; + window_flags |= ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoCollapse; + window_flags |= ImGuiWindowFlags_NoTitleBar; + + SetNextWindowBgAlpha(0.0f); // Don't set ImGuiWindowFlags_NoBackground because it disables borders + PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + Begin(window_label, NULL, window_flags); + PopStyleVar(); + beginned_into_host_window = true; + + host_window = g.CurrentWindow; + DockNodeSetupHostWindow(node, host_window); + host_window->DC.CursorPos = host_window->Pos; + node->Pos = host_window->Pos; + node->Size = host_window->Size; + + // We set ImGuiWindowFlags_NoFocusOnAppearing because we don't want the host window to take full focus (e.g. steal NavWindow) + // But we still it bring it to the front of display. There's no way to choose this precise behavior via window flags. + // One simple case to ponder if: window A has a toggle to create windows B/C/D. Dock B/C/D together, clear the toggle and enable it again. + // When reappearing B/C/D will request focus and be moved to the top of the display pile, but they are not linked to the dock host window + // during the frame they appear. The dock host window would keep its old display order, and the sorting in EndFrame would move B/C/D back + // after the dock host window, losing their top-most status. + if (node->HostWindow->Appearing) + BringWindowToDisplayFront(node->HostWindow); + + node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Auto; + } + else if (node->ParentNode) + { + node->HostWindow = host_window = node->ParentNode->HostWindow; + node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Auto; + } + if (node->WantMouseMove && node->HostWindow) + DockNodeStartMouseMovingWindow(node, node->HostWindow); + } + node->RefViewportId = 0; // Clear when we have a host window + + // Update focused node (the one whose title bar is highlight) within a node tree + if (node->IsSplitNode()) + IM_ASSERT(node->TabBar == NULL); + if (node->IsRootNode()) + if (ImGuiWindow* p_window = g.NavWindow ? g.NavWindow->RootWindow : NULL) + while (p_window != NULL && p_window->DockNode != NULL) + { + ImGuiDockNode* p_node = DockNodeGetRootNode(p_window->DockNode); + if (p_node == node) + { + node->LastFocusedNodeId = p_window->DockNode->ID; // Note: not using root node ID! + break; + } + p_window = p_node->HostWindow ? p_node->HostWindow->RootWindow : NULL; + } + + // Register a hit-test hole in the window unless we are currently dragging a window that is compatible with our dockspace + ImGuiDockNode* central_node = node->CentralNode; + const bool central_node_hole = node->IsRootNode() && host_window && (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0 && central_node != NULL && central_node->IsEmpty(); + bool central_node_hole_register_hit_test_hole = central_node_hole; + if (central_node_hole) + if (const ImGuiPayload* payload = ImGui::GetDragDropPayload()) + if (payload->IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) && DockNodeIsDropAllowed(host_window, *(ImGuiWindow**)payload->Data)) + central_node_hole_register_hit_test_hole = false; + if (central_node_hole_register_hit_test_hole) + { + // We add a little padding to match the "resize from edges" behavior and allow grabbing the splitter easily. + // (But we only add it if there's something else on the other side of the hole, otherwise for e.g. fullscreen + // covering passthru node we'd have a gap on the edge not covered by the hole) + IM_ASSERT(node->IsDockSpace()); // We cannot pass this flag without the DockSpace() api. Testing this because we also setup the hole in host_window->ParentNode + ImGuiDockNode* root_node = DockNodeGetRootNode(central_node); + ImRect root_rect(root_node->Pos, root_node->Pos + root_node->Size); + ImRect hole_rect(central_node->Pos, central_node->Pos + central_node->Size); + if (hole_rect.Min.x > root_rect.Min.x) { hole_rect.Min.x += WINDOWS_HOVER_PADDING; } + if (hole_rect.Max.x < root_rect.Max.x) { hole_rect.Max.x -= WINDOWS_HOVER_PADDING; } + if (hole_rect.Min.y > root_rect.Min.y) { hole_rect.Min.y += WINDOWS_HOVER_PADDING; } + if (hole_rect.Max.y < root_rect.Max.y) { hole_rect.Max.y -= WINDOWS_HOVER_PADDING; } + //GetForegroundDrawList()->AddRect(hole_rect.Min, hole_rect.Max, IM_COL32(255, 0, 0, 255)); + if (central_node_hole && !hole_rect.IsInverted()) + { + SetWindowHitTestHole(host_window, hole_rect.Min, hole_rect.Max - hole_rect.Min); + if (host_window->ParentWindow) + SetWindowHitTestHole(host_window->ParentWindow, hole_rect.Min, hole_rect.Max - hole_rect.Min); + } + } + + // Update position/size, process and draw resizing splitters + if (node->IsRootNode() && host_window) + { + DockNodeTreeUpdatePosSize(node, host_window->Pos, host_window->Size); + PushStyleColor(ImGuiCol_Separator, g.Style.Colors[ImGuiCol_Border]); + PushStyleColor(ImGuiCol_SeparatorActive, g.Style.Colors[ImGuiCol_ResizeGripActive]); + PushStyleColor(ImGuiCol_SeparatorHovered, g.Style.Colors[ImGuiCol_ResizeGripHovered]); + DockNodeTreeUpdateSplitter(node); + PopStyleColor(3); + } + + // Draw empty node background (currently can only be the Central Node) + if (host_window && node->IsEmpty() && node->IsVisible) + { + host_window->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG); + node->LastBgColor = (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) ? 0 : GetColorU32(ImGuiCol_DockingEmptyBg); + if (node->LastBgColor != 0) + host_window->DrawList->AddRectFilled(node->Pos, node->Pos + node->Size, node->LastBgColor); + node->IsBgDrawnThisFrame = true; + } + + // Draw whole dockspace background if ImGuiDockNodeFlags_PassthruCentralNode if set. + // We need to draw a background at the root level if requested by ImGuiDockNodeFlags_PassthruCentralNode, but we will only know the correct pos/size + // _after_ processing the resizing splitters. So we are using the DrawList channel splitting facility to submit drawing primitives out of order! + const bool render_dockspace_bg = node->IsRootNode() && host_window && (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0; + if (render_dockspace_bg && node->IsVisible) + { + host_window->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG); + if (central_node_hole) + RenderRectFilledWithHole(host_window->DrawList, node->Rect(), central_node->Rect(), GetColorU32(ImGuiCol_WindowBg), 0.0f); + else + host_window->DrawList->AddRectFilled(node->Pos, node->Pos + node->Size, GetColorU32(ImGuiCol_WindowBg), 0.0f); + } + + // Draw and populate Tab Bar + if (host_window) + host_window->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_FG); + if (host_window && node->Windows.Size > 0) + { + DockNodeUpdateTabBar(node, host_window); + } + else + { + node->WantCloseAll = false; + node->WantCloseTabId = 0; + node->IsFocused = false; + } + if (node->TabBar && node->TabBar->SelectedTabId) + node->SelectedTabId = node->TabBar->SelectedTabId; + else if (node->Windows.Size > 0) + node->SelectedTabId = node->Windows[0]->TabId; + + // Draw payload drop target + if (host_window && node->IsVisible) + if (node->IsRootNode() && (g.MovingWindow == NULL || g.MovingWindow->RootWindowDockTree != host_window)) + BeginDockableDragDropTarget(host_window); + + // We update this after DockNodeUpdateTabBar() + node->LastFrameActive = g.FrameCount; + + // Recurse into children + // FIXME-DOCK FIXME-OPT: Should not need to recurse into children + if (host_window) + { + if (node->ChildNodes[0]) + DockNodeUpdate(node->ChildNodes[0]); + if (node->ChildNodes[1]) + DockNodeUpdate(node->ChildNodes[1]); + + // Render outer borders last (after the tab bar) + if (node->IsRootNode()) + RenderWindowOuterBorders(host_window); + } + + // End host window + if (beginned_into_host_window) //-V1020 + End(); +} + +// Compare TabItem nodes given the last known DockOrder (will persist in .ini file as hint), used to sort tabs when multiple tabs are added on the same frame. +static int IMGUI_CDECL TabItemComparerByDockOrder(const void* lhs, const void* rhs) +{ + ImGuiWindow* a = ((const ImGuiTabItem*)lhs)->Window; + ImGuiWindow* b = ((const ImGuiTabItem*)rhs)->Window; + if (int d = ((a->DockOrder == -1) ? INT_MAX : a->DockOrder) - ((b->DockOrder == -1) ? INT_MAX : b->DockOrder)) + return d; + return (a->BeginOrderWithinContext - b->BeginOrderWithinContext); +} + +// Default handler for g.DockNodeWindowMenuHandler(): display the list of windows for a given dock-node. +// This is exceptionally stored in a function pointer to also user applications to tweak this menu (undocumented) +// Custom overrides may want to decorate, group, sort entries. +// Please note those are internal structures: if you copy this expect occasional breakage. +// (if you don't need to modify the "Tabs.Size == 1" behavior/path it is recommend you call this function in your handler) +void ImGui::DockNodeWindowMenuHandler_Default(ImGuiContext* ctx, ImGuiDockNode* node, ImGuiTabBar* tab_bar) +{ + IM_UNUSED(ctx); + if (tab_bar->Tabs.Size == 1) + { + // "Hide tab bar" option. Being one of our rare user-facing string we pull it from a table. + if (MenuItem(LocalizeGetMsg(ImGuiLocKey_DockingHideTabBar), NULL, node->IsHiddenTabBar())) + node->WantHiddenTabBarToggle = true; + } + else + { + // Display a selectable list of windows in this docking node + for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; + if (tab->Flags & ImGuiTabItemFlags_Button) + continue; + if (Selectable(TabBarGetTabName(tab_bar, tab), tab->ID == tab_bar->SelectedTabId)) + TabBarQueueFocus(tab_bar, tab); + SameLine(); + Text(" "); + } + } +} + +static void ImGui::DockNodeWindowMenuUpdate(ImGuiDockNode* node, ImGuiTabBar* tab_bar) +{ + // Try to position the menu so it is more likely to stays within the same viewport + ImGuiContext& g = *GImGui; + if (g.Style.WindowMenuButtonPosition == ImGuiDir_Left) + SetNextWindowPos(ImVec2(node->Pos.x, node->Pos.y + GetFrameHeight()), ImGuiCond_Always, ImVec2(0.0f, 0.0f)); + else + SetNextWindowPos(ImVec2(node->Pos.x + node->Size.x, node->Pos.y + GetFrameHeight()), ImGuiCond_Always, ImVec2(1.0f, 0.0f)); + if (BeginPopup("#WindowMenu")) + { + node->IsFocused = true; + g.DockNodeWindowMenuHandler(&g, node, tab_bar); + EndPopup(); + } +} + +// User helper to append/amend into a dock node tab bar. Most commonly used to add e.g. a "+" button. +bool ImGui::DockNodeBeginAmendTabBar(ImGuiDockNode* node) +{ + if (node->TabBar == NULL || node->HostWindow == NULL) + return false; + if (node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly) + return false; + if (node->TabBar->ID == 0) + return false; + Begin(node->HostWindow->Name); + PushOverrideID(node->ID); + bool ret = BeginTabBarEx(node->TabBar, node->TabBar->BarRect, node->TabBar->Flags); + IM_UNUSED(ret); + IM_ASSERT(ret); + return true; +} + +void ImGui::DockNodeEndAmendTabBar() +{ + EndTabBar(); + PopID(); + End(); +} + +static bool IsDockNodeTitleBarHighlighted(ImGuiDockNode* node, ImGuiDockNode* root_node) +{ + // CTRL+Tab highlight (only highlighting leaf node, not whole hierarchy) + ImGuiContext& g = *GImGui; + if (g.NavWindowingTarget) + return (g.NavWindowingTarget->DockNode == node); + + // FIXME-DOCKING: May want alternative to treat central node void differently? e.g. if (g.NavWindow == host_window) + if (g.NavWindow && root_node->LastFocusedNodeId == node->ID) + { + // FIXME: This could all be backed in RootWindowForTitleBarHighlight? Probably need to reorganize for both dock nodes + other RootWindowForTitleBarHighlight users (not-node) + ImGuiWindow* parent_window = g.NavWindow->RootWindow; + while (parent_window->Flags & ImGuiWindowFlags_ChildMenu) + parent_window = parent_window->ParentWindow->RootWindow; + ImGuiDockNode* start_parent_node = parent_window->DockNodeAsHost ? parent_window->DockNodeAsHost : parent_window->DockNode; + for (ImGuiDockNode* parent_node = start_parent_node; parent_node != NULL; parent_node = parent_node->HostWindow ? parent_node->HostWindow->RootWindow->DockNode : NULL) + if ((parent_node = ImGui::DockNodeGetRootNode(parent_node)) == root_node) + return true; + } + return false; +} + +// Submit the tab bar corresponding to a dock node and various housekeeping details. +static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_window) +{ + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + + const bool node_was_active = (node->LastFrameActive + 1 == g.FrameCount); + const bool closed_all = node->WantCloseAll && node_was_active; + const ImGuiID closed_one = node->WantCloseTabId && node_was_active; + node->WantCloseAll = false; + node->WantCloseTabId = 0; + + // Decide if we should use a focused title bar color + bool is_focused = false; + ImGuiDockNode* root_node = DockNodeGetRootNode(node); + if (IsDockNodeTitleBarHighlighted(node, root_node)) + is_focused = true; + + // Hidden tab bar will show a triangle on the upper-left (in Begin) + if (node->IsHiddenTabBar() || node->IsNoTabBar()) + { + node->VisibleWindow = (node->Windows.Size > 0) ? node->Windows[0] : NULL; + node->IsFocused = is_focused; + if (is_focused) + node->LastFrameFocused = g.FrameCount; + if (node->VisibleWindow) + { + // Notify root of visible window (used to display title in OS task bar) + if (is_focused || root_node->VisibleWindow == NULL) + root_node->VisibleWindow = node->VisibleWindow; + if (node->TabBar) + node->TabBar->VisibleTabId = node->VisibleWindow->TabId; + } + return; + } + + // Move ourselves to the Menu layer (so we can be accessed by tapping Alt) + undo SkipItems flag in order to draw over the title bar even if the window is collapsed + bool backup_skip_item = host_window->SkipItems; + if (!node->IsDockSpace()) + { + host_window->SkipItems = false; + host_window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; + } + + // Use PushOverrideID() instead of PushID() to use the node id _without_ the host window ID. + // This is to facilitate computing those ID from the outside, and will affect more or less only the ID of the collapse button, popup and tabs, + // as docked windows themselves will override the stack with their own root ID. + PushOverrideID(node->ID); + ImGuiTabBar* tab_bar = node->TabBar; + bool tab_bar_is_recreated = (tab_bar == NULL); // Tab bar are automatically destroyed when a node gets hidden + if (tab_bar == NULL) + { + DockNodeAddTabBar(node); + tab_bar = node->TabBar; + } + + ImGuiID focus_tab_id = 0; + node->IsFocused = is_focused; + + const ImGuiDockNodeFlags node_flags = node->MergedFlags; + const bool has_window_menu_button = (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0 && (style.WindowMenuButtonPosition != ImGuiDir_None); + + // In a dock node, the Collapse Button turns into the Window Menu button. + // FIXME-DOCK FIXME-OPT: Could we recycle popups id across multiple dock nodes? + if (has_window_menu_button && IsPopupOpen("#WindowMenu")) + { + ImGuiID next_selected_tab_id = tab_bar->NextSelectedTabId; + DockNodeWindowMenuUpdate(node, tab_bar); + if (tab_bar->NextSelectedTabId != 0 && tab_bar->NextSelectedTabId != next_selected_tab_id) + focus_tab_id = tab_bar->NextSelectedTabId; + is_focused |= node->IsFocused; + } + + // Layout + ImRect title_bar_rect, tab_bar_rect; + ImVec2 window_menu_button_pos; + ImVec2 close_button_pos; + DockNodeCalcTabBarLayout(node, &title_bar_rect, &tab_bar_rect, &window_menu_button_pos, &close_button_pos); + + // Submit new tabs, they will be added as Unsorted and sorted below based on relative DockOrder value. + const int tabs_count_old = tab_bar->Tabs.Size; + for (int window_n = 0; window_n < node->Windows.Size; window_n++) + { + ImGuiWindow* window = node->Windows[window_n]; + if (TabBarFindTabByID(tab_bar, window->TabId) == NULL) + TabBarAddTab(tab_bar, ImGuiTabItemFlags_Unsorted, window); + } + + // Title bar + if (is_focused) + node->LastFrameFocused = g.FrameCount; + ImU32 title_bar_col = GetColorU32(host_window->Collapsed ? ImGuiCol_TitleBgCollapsed : is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg); + ImDrawFlags rounding_flags = CalcRoundingFlagsForRectInRect(title_bar_rect, host_window->Rect(), g.Style.DockingSeparatorSize); + host_window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, host_window->WindowRounding, rounding_flags); + + // Docking/Collapse button + if (has_window_menu_button) + { + if (CollapseButton(host_window->GetID("#COLLAPSE"), window_menu_button_pos, node)) // == DockNodeGetWindowMenuButtonId(node) + OpenPopup("#WindowMenu"); + if (IsItemActive()) + focus_tab_id = tab_bar->SelectedTabId; + if (IsItemHovered(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_DelayNormal) && g.HoveredIdTimer > 0.5f) + SetTooltip("%s", LocalizeGetMsg(ImGuiLocKey_DockingDragToUndockOrMoveNode)); + } + + // If multiple tabs are appearing on the same frame, sort them based on their persistent DockOrder value + int tabs_unsorted_start = tab_bar->Tabs.Size; + for (int tab_n = tab_bar->Tabs.Size - 1; tab_n >= 0 && (tab_bar->Tabs[tab_n].Flags & ImGuiTabItemFlags_Unsorted); tab_n--) + { + // FIXME-DOCK: Consider only clearing the flag after the tab has been alive for a few consecutive frames, allowing late comers to not break sorting? + tab_bar->Tabs[tab_n].Flags &= ~ImGuiTabItemFlags_Unsorted; + tabs_unsorted_start = tab_n; + } + if (tab_bar->Tabs.Size > tabs_unsorted_start) + { + IMGUI_DEBUG_LOG_DOCKING("[docking] In node 0x%08X: %d new appearing tabs:%s\n", node->ID, tab_bar->Tabs.Size - tabs_unsorted_start, (tab_bar->Tabs.Size > tabs_unsorted_start + 1) ? " (will sort)" : ""); + for (int tab_n = tabs_unsorted_start; tab_n < tab_bar->Tabs.Size; tab_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; + IM_UNUSED(tab); + IMGUI_DEBUG_LOG_DOCKING("[docking] - Tab 0x%08X '%s' Order %d\n", tab->ID, TabBarGetTabName(tab_bar, tab), tab->Window ? tab->Window->DockOrder : -1); + } + IMGUI_DEBUG_LOG_DOCKING("[docking] SelectedTabId = 0x%08X, NavWindow->TabId = 0x%08X\n", node->SelectedTabId, g.NavWindow ? g.NavWindow->TabId : -1); + if (tab_bar->Tabs.Size > tabs_unsorted_start + 1) + ImQsort(tab_bar->Tabs.Data + tabs_unsorted_start, tab_bar->Tabs.Size - tabs_unsorted_start, sizeof(ImGuiTabItem), TabItemComparerByDockOrder); + } + + // Apply NavWindow focus back to the tab bar + if (g.NavWindow && g.NavWindow->RootWindow->DockNode == node) + tab_bar->SelectedTabId = g.NavWindow->RootWindow->TabId; + + // Selected newly added tabs, or persistent tab ID if the tab bar was just recreated + if (tab_bar_is_recreated && TabBarFindTabByID(tab_bar, node->SelectedTabId) != NULL) + tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = node->SelectedTabId; + else if (tab_bar->Tabs.Size > tabs_count_old) + tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = tab_bar->Tabs.back().Window->TabId; + + // Begin tab bar + ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_AutoSelectNewTabs; // | ImGuiTabBarFlags_NoTabListScrollingButtons); + tab_bar_flags |= ImGuiTabBarFlags_SaveSettings | ImGuiTabBarFlags_DockNode;// | ImGuiTabBarFlags_FittingPolicyScroll; + tab_bar_flags |= ImGuiTabBarFlags_DrawSelectedOverline; + if (!host_window->Collapsed && is_focused) + tab_bar_flags |= ImGuiTabBarFlags_IsFocused; + tab_bar->ID = GetID("#TabBar"); + tab_bar->SeparatorMinX = node->Pos.x + host_window->WindowBorderSize; // Separator cover the whole node width + tab_bar->SeparatorMaxX = node->Pos.x + node->Size.x - host_window->WindowBorderSize; + BeginTabBarEx(tab_bar, tab_bar_rect, tab_bar_flags); + //host_window->DrawList->AddRect(tab_bar_rect.Min, tab_bar_rect.Max, IM_COL32(255,0,255,255)); + + // Backup style colors + ImVec4 backup_style_cols[ImGuiWindowDockStyleCol_COUNT]; + for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++) + backup_style_cols[color_n] = g.Style.Colors[GWindowDockStyleColors[color_n]]; + + // Submit actual tabs + node->VisibleWindow = NULL; + for (int window_n = 0; window_n < node->Windows.Size; window_n++) + { + ImGuiWindow* window = node->Windows[window_n]; + if ((closed_all || closed_one == window->TabId) && window->HasCloseButton && !(window->Flags & ImGuiWindowFlags_UnsavedDocument)) + continue; + if (window->LastFrameActive + 1 >= g.FrameCount || !node_was_active) + { + ImGuiTabItemFlags tab_item_flags = 0; + tab_item_flags |= window->WindowClass.TabItemFlagsOverrideSet; + if (window->Flags & ImGuiWindowFlags_UnsavedDocument) + tab_item_flags |= ImGuiTabItemFlags_UnsavedDocument; + if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton) + tab_item_flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton; + + // Apply stored style overrides for the window + for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++) + g.Style.Colors[GWindowDockStyleColors[color_n]] = ColorConvertU32ToFloat4(window->DockStyle.Colors[color_n]); + + // Note that TabItemEx() calls TabBarCalcTabID() so our tab item ID will ignore the current ID stack (rightly so) + bool tab_open = true; + TabItemEx(tab_bar, window->Name, window->HasCloseButton ? &tab_open : NULL, tab_item_flags, window); + if (!tab_open) + node->WantCloseTabId = window->TabId; + if (tab_bar->VisibleTabId == window->TabId) + node->VisibleWindow = window; + + // Store last item data so it can be queried with IsItemXXX functions after the user Begin() call + window->DockTabItemStatusFlags = g.LastItemData.StatusFlags; + window->DockTabItemRect = g.LastItemData.Rect; + + // Update navigation ID on menu layer + if (g.NavWindow && g.NavWindow->RootWindow == window && (window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) == 0) + host_window->NavLastIds[1] = window->TabId; + } + } + + // Restore style colors + for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++) + g.Style.Colors[GWindowDockStyleColors[color_n]] = backup_style_cols[color_n]; + + // Notify root of visible window (used to display title in OS task bar) + if (node->VisibleWindow) + if (is_focused || root_node->VisibleWindow == NULL) + root_node->VisibleWindow = node->VisibleWindow; + + // Close button (after VisibleWindow was updated) + // Note that VisibleWindow may have been overrided by CTRL+Tabbing, so VisibleWindow->TabId may be != from tab_bar->SelectedTabId + const bool close_button_is_enabled = node->HasCloseButton && node->VisibleWindow && node->VisibleWindow->HasCloseButton; + const bool close_button_is_visible = node->HasCloseButton; + //const bool close_button_is_visible = close_button_is_enabled; // Most people would expect this behavior of not even showing the button (leaving a hole since we can't claim that space as other windows in the tba bar have one) + if (close_button_is_visible) + { + if (!close_button_is_enabled) + { + PushItemFlag(ImGuiItemFlags_Disabled, true); + PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_Text] * ImVec4(1.0f,1.0f,1.0f,0.4f)); + } + if (CloseButton(host_window->GetID("#CLOSE"), close_button_pos)) + { + node->WantCloseAll = true; + for (int n = 0; n < tab_bar->Tabs.Size; n++) + TabBarCloseTab(tab_bar, &tab_bar->Tabs[n]); + } + //if (IsItemActive()) + // focus_tab_id = tab_bar->SelectedTabId; + if (!close_button_is_enabled) + { + PopStyleColor(); + PopItemFlag(); + } + } + + // When clicking on the title bar outside of tabs, we still focus the selected tab for that node + // FIXME: TabItems submitted earlier use AllowItemOverlap so we manually perform a more specific test for now (hovered || held) in order to not cover them. + ImGuiID title_bar_id = host_window->GetID("#TITLEBAR"); + if (g.HoveredId == 0 || g.HoveredId == title_bar_id || g.ActiveId == title_bar_id) + { + // AllowOverlap mode required for appending into dock node tab bar, + // otherwise dragging window will steal HoveredId and amended tabs cannot get them. + bool held; + KeepAliveID(title_bar_id); + ButtonBehavior(title_bar_rect, title_bar_id, NULL, &held, ImGuiButtonFlags_AllowOverlap); + if (g.HoveredId == title_bar_id) + { + g.LastItemData.ID = title_bar_id; + } + if (held) + { + if (IsMouseClicked(0)) + focus_tab_id = tab_bar->SelectedTabId; + + // Forward moving request to selected window + if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId)) + StartMouseMovingWindowOrNode(tab->Window ? tab->Window : node->HostWindow, node, false); // Undock from tab bar empty space + } + } + + // Forward focus from host node to selected window + //if (is_focused && g.NavWindow == host_window && !g.NavWindowingTarget) + // focus_tab_id = tab_bar->SelectedTabId; + + // When clicked on a tab we requested focus to the docked child + // This overrides the value set by "forward focus from host node to selected window". + if (tab_bar->NextSelectedTabId) + focus_tab_id = tab_bar->NextSelectedTabId; + + // Apply navigation focus + if (focus_tab_id != 0) + if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, focus_tab_id)) + if (tab->Window) + { + FocusWindow(tab->Window); + NavInitWindow(tab->Window, false); + } + + EndTabBar(); + PopID(); + + // Restore SkipItems flag + if (!node->IsDockSpace()) + { + host_window->DC.NavLayerCurrent = ImGuiNavLayer_Main; + host_window->SkipItems = backup_skip_item; + } +} + +static void ImGui::DockNodeAddTabBar(ImGuiDockNode* node) +{ + IM_ASSERT(node->TabBar == NULL); + node->TabBar = IM_NEW(ImGuiTabBar); +} + +static void ImGui::DockNodeRemoveTabBar(ImGuiDockNode* node) +{ + if (node->TabBar == NULL) + return; + IM_DELETE(node->TabBar); + node->TabBar = NULL; +} + +static bool DockNodeIsDropAllowedOne(ImGuiWindow* payload, ImGuiWindow* host_window) +{ + if (host_window->DockNodeAsHost && host_window->DockNodeAsHost->IsDockSpace() && payload->BeginOrderWithinContext < host_window->BeginOrderWithinContext) + return false; + + ImGuiWindowClass* host_class = host_window->DockNodeAsHost ? &host_window->DockNodeAsHost->WindowClass : &host_window->WindowClass; + ImGuiWindowClass* payload_class = &payload->WindowClass; + if (host_class->ClassId != payload_class->ClassId) + { + bool pass = false; + if (host_class->ClassId != 0 && host_class->DockingAllowUnclassed && payload_class->ClassId == 0) + pass = true; + if (payload_class->ClassId != 0 && payload_class->DockingAllowUnclassed && host_class->ClassId == 0) + pass = true; + if (!pass) + return false; + } + + // Prevent docking any window created above a popup + // Technically we should support it (e.g. in the case of a long-lived modal window that had fancy docking features), + // by e.g. adding a 'if (!ImGui::IsWindowWithinBeginStackOf(host_window, popup_window))' test. + // But it would requires more work on our end because the dock host windows is technically created in NewFrame() + // and our ->ParentXXX and ->RootXXX pointers inside windows are currently mislading or lacking. + ImGuiContext& g = *GImGui; + for (int i = g.OpenPopupStack.Size - 1; i >= 0; i--) + if (ImGuiWindow* popup_window = g.OpenPopupStack[i].Window) + if (ImGui::IsWindowWithinBeginStackOf(payload, popup_window)) // Payload is created from within a popup begin stack. + return false; + + return true; +} + +static bool ImGui::DockNodeIsDropAllowed(ImGuiWindow* host_window, ImGuiWindow* root_payload) +{ + if (root_payload->DockNodeAsHost && root_payload->DockNodeAsHost->IsSplitNode()) // FIXME-DOCK: Missing filtering + return true; + + const int payload_count = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->Windows.Size : 1; + for (int payload_n = 0; payload_n < payload_count; payload_n++) + { + ImGuiWindow* payload = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->Windows[payload_n] : root_payload; + if (DockNodeIsDropAllowedOne(payload, host_window)) + return true; + } + return false; +} + +// window menu button == collapse button when not in a dock node. +// FIXME: This is similar to RenderWindowTitleBarContents(), may want to share code. +static void ImGui::DockNodeCalcTabBarLayout(const ImGuiDockNode* node, ImRect* out_title_rect, ImRect* out_tab_bar_rect, ImVec2* out_window_menu_button_pos, ImVec2* out_close_button_pos) +{ + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + + ImRect r = ImRect(node->Pos.x, node->Pos.y, node->Pos.x + node->Size.x, node->Pos.y + g.FontSize + g.Style.FramePadding.y * 2.0f); + if (out_title_rect) { *out_title_rect = r; } + + r.Min.x += style.WindowBorderSize; + r.Max.x -= style.WindowBorderSize; + + float button_sz = g.FontSize; + r.Min.x += style.FramePadding.x; + r.Max.x -= style.FramePadding.x; + ImVec2 window_menu_button_pos = ImVec2(r.Min.x, r.Min.y + style.FramePadding.y); + if (node->HasCloseButton) + { + if (out_close_button_pos) *out_close_button_pos = ImVec2(r.Max.x - button_sz, r.Min.y + style.FramePadding.y); + r.Max.x -= button_sz + style.ItemInnerSpacing.x; + } + if (node->HasWindowMenuButton && style.WindowMenuButtonPosition == ImGuiDir_Left) + { + r.Min.x += button_sz + style.ItemInnerSpacing.x; + } + else if (node->HasWindowMenuButton && style.WindowMenuButtonPosition == ImGuiDir_Right) + { + window_menu_button_pos = ImVec2(r.Max.x - button_sz, r.Min.y + style.FramePadding.y); + r.Max.x -= button_sz + style.ItemInnerSpacing.x; + } + if (out_tab_bar_rect) { *out_tab_bar_rect = r; } + if (out_window_menu_button_pos) { *out_window_menu_button_pos = window_menu_button_pos; } +} + +void ImGui::DockNodeCalcSplitRects(ImVec2& pos_old, ImVec2& size_old, ImVec2& pos_new, ImVec2& size_new, ImGuiDir dir, ImVec2 size_new_desired) +{ + ImGuiContext& g = *GImGui; + const float dock_spacing = g.Style.ItemInnerSpacing.x; + const ImGuiAxis axis = (dir == ImGuiDir_Left || dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y; + pos_new[axis ^ 1] = pos_old[axis ^ 1]; + size_new[axis ^ 1] = size_old[axis ^ 1]; + + // Distribute size on given axis (with a desired size or equally) + const float w_avail = size_old[axis] - dock_spacing; + if (size_new_desired[axis] > 0.0f && size_new_desired[axis] <= w_avail * 0.5f) + { + size_new[axis] = size_new_desired[axis]; + size_old[axis] = IM_TRUNC(w_avail - size_new[axis]); + } + else + { + size_new[axis] = IM_TRUNC(w_avail * 0.5f); + size_old[axis] = IM_TRUNC(w_avail - size_new[axis]); + } + + // Position each node + if (dir == ImGuiDir_Right || dir == ImGuiDir_Down) + { + pos_new[axis] = pos_old[axis] + size_old[axis] + dock_spacing; + } + else if (dir == ImGuiDir_Left || dir == ImGuiDir_Up) + { + pos_new[axis] = pos_old[axis]; + pos_old[axis] = pos_new[axis] + size_new[axis] + dock_spacing; + } +} + +// Retrieve the drop rectangles for a given direction or for the center + perform hit testing. +bool ImGui::DockNodeCalcDropRectsAndTestMousePos(const ImRect& parent, ImGuiDir dir, ImRect& out_r, bool outer_docking, ImVec2* test_mouse_pos) +{ + ImGuiContext& g = *GImGui; + + const float parent_smaller_axis = ImMin(parent.GetWidth(), parent.GetHeight()); + const float hs_for_central_nodes = ImMin(g.FontSize * 1.5f, ImMax(g.FontSize * 0.5f, parent_smaller_axis / 8.0f)); + float hs_w; // Half-size, longer axis + float hs_h; // Half-size, smaller axis + ImVec2 off; // Distance from edge or center + if (outer_docking) + { + //hs_w = ImTrunc(ImClamp(parent_smaller_axis - hs_for_central_nodes * 4.0f, g.FontSize * 0.5f, g.FontSize * 8.0f)); + //hs_h = ImTrunc(hs_w * 0.15f); + //off = ImVec2(ImTrunc(parent.GetWidth() * 0.5f - GetFrameHeightWithSpacing() * 1.4f - hs_h), ImTrunc(parent.GetHeight() * 0.5f - GetFrameHeightWithSpacing() * 1.4f - hs_h)); + hs_w = ImTrunc(hs_for_central_nodes * 1.50f); + hs_h = ImTrunc(hs_for_central_nodes * 0.80f); + off = ImTrunc(ImVec2(parent.GetWidth() * 0.5f - hs_h, parent.GetHeight() * 0.5f - hs_h)); + } + else + { + hs_w = ImTrunc(hs_for_central_nodes); + hs_h = ImTrunc(hs_for_central_nodes * 0.90f); + off = ImTrunc(ImVec2(hs_w * 2.40f, hs_w * 2.40f)); + } + + ImVec2 c = ImTrunc(parent.GetCenter()); + if (dir == ImGuiDir_None) { out_r = ImRect(c.x - hs_w, c.y - hs_w, c.x + hs_w, c.y + hs_w); } + else if (dir == ImGuiDir_Up) { out_r = ImRect(c.x - hs_w, c.y - off.y - hs_h, c.x + hs_w, c.y - off.y + hs_h); } + else if (dir == ImGuiDir_Down) { out_r = ImRect(c.x - hs_w, c.y + off.y - hs_h, c.x + hs_w, c.y + off.y + hs_h); } + else if (dir == ImGuiDir_Left) { out_r = ImRect(c.x - off.x - hs_h, c.y - hs_w, c.x - off.x + hs_h, c.y + hs_w); } + else if (dir == ImGuiDir_Right) { out_r = ImRect(c.x + off.x - hs_h, c.y - hs_w, c.x + off.x + hs_h, c.y + hs_w); } + + if (test_mouse_pos == NULL) + return false; + + ImRect hit_r = out_r; + if (!outer_docking) + { + // Custom hit testing for the 5-way selection, designed to reduce flickering when moving diagonally between sides + hit_r.Expand(ImTrunc(hs_w * 0.30f)); + ImVec2 mouse_delta = (*test_mouse_pos - c); + float mouse_delta_len2 = ImLengthSqr(mouse_delta); + float r_threshold_center = hs_w * 1.4f; + float r_threshold_sides = hs_w * (1.4f + 1.2f); + if (mouse_delta_len2 < r_threshold_center * r_threshold_center) + return (dir == ImGuiDir_None); + if (mouse_delta_len2 < r_threshold_sides * r_threshold_sides) + return (dir == ImGetDirQuadrantFromDelta(mouse_delta.x, mouse_delta.y)); + } + return hit_r.Contains(*test_mouse_pos); +} + +// host_node may be NULL if the window doesn't have a DockNode already. +// FIXME-DOCK: This is misnamed since it's also doing the filtering. +static void ImGui::DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, ImGuiDockNode* payload_node, ImGuiDockPreviewData* data, bool is_explicit_target, bool is_outer_docking) +{ + ImGuiContext& g = *GImGui; + + // There is an edge case when docking into a dockspace which only has inactive nodes. + // In this case DockNodeTreeFindNodeByPos() will have selected a leaf node which is inactive. + // Because the inactive leaf node doesn't have proper pos/size yet, we'll use the root node as reference. + if (payload_node == NULL) + payload_node = payload_window->DockNodeAsHost; + ImGuiDockNode* ref_node_for_rect = (host_node && !host_node->IsVisible) ? DockNodeGetRootNode(host_node) : host_node; + if (ref_node_for_rect) + IM_ASSERT(ref_node_for_rect->IsVisible == true); + + // Filter, figure out where we are allowed to dock + ImGuiDockNodeFlags src_node_flags = payload_node ? payload_node->MergedFlags : payload_window->WindowClass.DockNodeFlagsOverrideSet; + ImGuiDockNodeFlags dst_node_flags = host_node ? host_node->MergedFlags : host_window->WindowClass.DockNodeFlagsOverrideSet; + data->IsCenterAvailable = true; + if (is_outer_docking) + data->IsCenterAvailable = false; + else if (dst_node_flags & ImGuiDockNodeFlags_NoDockingOverMe) + data->IsCenterAvailable = false; + else if (host_node && (dst_node_flags & ImGuiDockNodeFlags_NoDockingOverCentralNode) && host_node->IsCentralNode()) + data->IsCenterAvailable = false; + else if ((!host_node || !host_node->IsEmpty()) && payload_node && payload_node->IsSplitNode() && (payload_node->OnlyNodeWithWindows == NULL)) // Is _visibly_ split? + data->IsCenterAvailable = false; + else if ((src_node_flags & ImGuiDockNodeFlags_NoDockingOverOther) && (!host_node || !host_node->IsEmpty())) + data->IsCenterAvailable = false; + else if ((src_node_flags & ImGuiDockNodeFlags_NoDockingOverEmpty) && host_node && host_node->IsEmpty()) + data->IsCenterAvailable = false; + + data->IsSidesAvailable = true; + if ((dst_node_flags & ImGuiDockNodeFlags_NoDockingSplit) || g.IO.ConfigDockingNoSplit) + data->IsSidesAvailable = false; + else if (!is_outer_docking && host_node && host_node->ParentNode == NULL && host_node->IsCentralNode()) + data->IsSidesAvailable = false; + else if (src_node_flags & ImGuiDockNodeFlags_NoDockingSplitOther) + data->IsSidesAvailable = false; + + // Build a tentative future node (reuse same structure because it is practical. Shape will be readjusted when previewing a split) + data->FutureNode.HasCloseButton = (host_node ? host_node->HasCloseButton : host_window->HasCloseButton) || (payload_window->HasCloseButton); + data->FutureNode.HasWindowMenuButton = host_node ? true : ((host_window->Flags & ImGuiWindowFlags_NoCollapse) == 0); + data->FutureNode.Pos = ref_node_for_rect ? ref_node_for_rect->Pos : host_window->Pos; + data->FutureNode.Size = ref_node_for_rect ? ref_node_for_rect->Size : host_window->Size; + + // Calculate drop shapes geometry for allowed splitting directions + IM_ASSERT(ImGuiDir_None == -1); + data->SplitNode = host_node; + data->SplitDir = ImGuiDir_None; + data->IsSplitDirExplicit = false; + if (!host_window->Collapsed) + for (int dir = ImGuiDir_None; dir < ImGuiDir_COUNT; dir++) + { + if (dir == ImGuiDir_None && !data->IsCenterAvailable) + continue; + if (dir != ImGuiDir_None && !data->IsSidesAvailable) + continue; + if (DockNodeCalcDropRectsAndTestMousePos(data->FutureNode.Rect(), (ImGuiDir)dir, data->DropRectsDraw[dir+1], is_outer_docking, &g.IO.MousePos)) + { + data->SplitDir = (ImGuiDir)dir; + data->IsSplitDirExplicit = true; + } + } + + // When docking without holding Shift, we only allow and preview docking when hovering over a drop rect or over the title bar + data->IsDropAllowed = (data->SplitDir != ImGuiDir_None) || (data->IsCenterAvailable); + if (!is_explicit_target && !data->IsSplitDirExplicit && !g.IO.ConfigDockingWithShift) + data->IsDropAllowed = false; + + // Calculate split area + data->SplitRatio = 0.0f; + if (data->SplitDir != ImGuiDir_None) + { + ImGuiDir split_dir = data->SplitDir; + ImGuiAxis split_axis = (split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y; + ImVec2 pos_new, pos_old = data->FutureNode.Pos; + ImVec2 size_new, size_old = data->FutureNode.Size; + DockNodeCalcSplitRects(pos_old, size_old, pos_new, size_new, split_dir, payload_window->Size); + + // Calculate split ratio so we can pass it down the docking request + float split_ratio = ImSaturate(size_new[split_axis] / data->FutureNode.Size[split_axis]); + data->FutureNode.Pos = pos_new; + data->FutureNode.Size = size_new; + data->SplitRatio = (split_dir == ImGuiDir_Right || split_dir == ImGuiDir_Down) ? (1.0f - split_ratio) : (split_ratio); + } +} + +static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* root_payload, const ImGuiDockPreviewData* data) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.CurrentWindow == host_window); // Because we rely on font size to calculate tab sizes + + // With this option, we only display the preview on the target viewport, and the payload viewport is made transparent. + // To compensate for the single layer obstructed by the payload, we'll increase the alpha of the preview nodes. + const bool is_transparent_payload = g.IO.ConfigDockingTransparentPayload; + + // In case the two windows involved are on different viewports, we will draw the overlay on each of them. + int overlay_draw_lists_count = 0; + ImDrawList* overlay_draw_lists[2]; + overlay_draw_lists[overlay_draw_lists_count++] = GetForegroundDrawList(host_window->Viewport); + if (host_window->Viewport != root_payload->Viewport && !is_transparent_payload) + overlay_draw_lists[overlay_draw_lists_count++] = GetForegroundDrawList(root_payload->Viewport); + + // Draw main preview rectangle + const ImU32 overlay_col_main = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 0.60f : 0.40f); + const ImU32 overlay_col_drop = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 0.90f : 0.70f); + const ImU32 overlay_col_drop_hovered = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 1.20f : 1.00f); + const ImU32 overlay_col_lines = GetColorU32(ImGuiCol_NavWindowingHighlight, is_transparent_payload ? 0.80f : 0.60f); + + // Display area preview + const bool can_preview_tabs = (root_payload->DockNodeAsHost == NULL || root_payload->DockNodeAsHost->Windows.Size > 0); + if (data->IsDropAllowed) + { + ImRect overlay_rect = data->FutureNode.Rect(); + if (data->SplitDir == ImGuiDir_None && can_preview_tabs) + overlay_rect.Min.y += GetFrameHeight(); + if (data->SplitDir != ImGuiDir_None || data->IsCenterAvailable) + for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++) + overlay_draw_lists[overlay_n]->AddRectFilled(overlay_rect.Min, overlay_rect.Max, overlay_col_main, host_window->WindowRounding, CalcRoundingFlagsForRectInRect(overlay_rect, host_window->Rect(), g.Style.DockingSeparatorSize)); + } + + // Display tab shape/label preview unless we are splitting node (it generally makes the situation harder to read) + if (data->IsDropAllowed && can_preview_tabs && data->SplitDir == ImGuiDir_None && data->IsCenterAvailable) + { + // Compute target tab bar geometry so we can locate our preview tabs + ImRect tab_bar_rect; + DockNodeCalcTabBarLayout(&data->FutureNode, NULL, &tab_bar_rect, NULL, NULL); + ImVec2 tab_pos = tab_bar_rect.Min; + if (host_node && host_node->TabBar) + { + if (!host_node->IsHiddenTabBar() && !host_node->IsNoTabBar()) + tab_pos.x += host_node->TabBar->WidthAllTabs + g.Style.ItemInnerSpacing.x; // We don't use OffsetNewTab because when using non-persistent-order tab bar it is incremented with each Tab submission. + else + tab_pos.x += g.Style.ItemInnerSpacing.x + TabItemCalcSize(host_node->Windows[0]).x; + } + else if (!(host_window->Flags & ImGuiWindowFlags_DockNodeHost)) + { + tab_pos.x += g.Style.ItemInnerSpacing.x + TabItemCalcSize(host_window).x; // Account for slight offset which will be added when changing from title bar to tab bar + } + + // Draw tab shape/label preview (payload may be a loose window or a host window carrying multiple tabbed windows) + if (root_payload->DockNodeAsHost) + IM_ASSERT(root_payload->DockNodeAsHost->Windows.Size <= root_payload->DockNodeAsHost->TabBar->Tabs.Size); + ImGuiTabBar* tab_bar_with_payload = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->TabBar : NULL; + const int payload_count = tab_bar_with_payload ? tab_bar_with_payload->Tabs.Size : 1; + for (int payload_n = 0; payload_n < payload_count; payload_n++) + { + // DockNode's TabBar may have non-window Tabs manually appended by user + ImGuiWindow* payload_window = tab_bar_with_payload ? tab_bar_with_payload->Tabs[payload_n].Window : root_payload; + if (tab_bar_with_payload && payload_window == NULL) + continue; + if (!DockNodeIsDropAllowedOne(payload_window, host_window)) + continue; + + // Calculate the tab bounding box for each payload window + ImVec2 tab_size = TabItemCalcSize(payload_window); + ImRect tab_bb(tab_pos.x, tab_pos.y, tab_pos.x + tab_size.x, tab_pos.y + tab_size.y); + tab_pos.x += tab_size.x + g.Style.ItemInnerSpacing.x; + const ImU32 overlay_col_text = GetColorU32(payload_window->DockStyle.Colors[ImGuiWindowDockStyleCol_Text]); + const ImU32 overlay_col_tabs = GetColorU32(payload_window->DockStyle.Colors[ImGuiWindowDockStyleCol_TabSelected]); + PushStyleColor(ImGuiCol_Text, overlay_col_text); + for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++) + { + ImGuiTabItemFlags tab_flags = (payload_window->Flags & ImGuiWindowFlags_UnsavedDocument) ? ImGuiTabItemFlags_UnsavedDocument : 0; + if (!tab_bar_rect.Contains(tab_bb)) + overlay_draw_lists[overlay_n]->PushClipRect(tab_bar_rect.Min, tab_bar_rect.Max); + TabItemBackground(overlay_draw_lists[overlay_n], tab_bb, tab_flags, overlay_col_tabs); + TabItemLabelAndCloseButton(overlay_draw_lists[overlay_n], tab_bb, tab_flags, g.Style.FramePadding, payload_window->Name, 0, 0, false, NULL, NULL); + if (!tab_bar_rect.Contains(tab_bb)) + overlay_draw_lists[overlay_n]->PopClipRect(); + } + PopStyleColor(); + } + } + + // Display drop boxes + const float overlay_rounding = ImMax(3.0f, g.Style.FrameRounding); + for (int dir = ImGuiDir_None; dir < ImGuiDir_COUNT; dir++) + { + if (!data->DropRectsDraw[dir + 1].IsInverted()) + { + ImRect draw_r = data->DropRectsDraw[dir + 1]; + ImRect draw_r_in = draw_r; + draw_r_in.Expand(-2.0f); + ImU32 overlay_col = (data->SplitDir == (ImGuiDir)dir && data->IsSplitDirExplicit) ? overlay_col_drop_hovered : overlay_col_drop; + for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++) + { + ImVec2 center = ImFloor(draw_r_in.GetCenter()); + overlay_draw_lists[overlay_n]->AddRectFilled(draw_r.Min, draw_r.Max, overlay_col, overlay_rounding); + overlay_draw_lists[overlay_n]->AddRect(draw_r_in.Min, draw_r_in.Max, overlay_col_lines, overlay_rounding); + if (dir == ImGuiDir_Left || dir == ImGuiDir_Right) + overlay_draw_lists[overlay_n]->AddLine(ImVec2(center.x, draw_r_in.Min.y), ImVec2(center.x, draw_r_in.Max.y), overlay_col_lines); + if (dir == ImGuiDir_Up || dir == ImGuiDir_Down) + overlay_draw_lists[overlay_n]->AddLine(ImVec2(draw_r_in.Min.x, center.y), ImVec2(draw_r_in.Max.x, center.y), overlay_col_lines); + } + } + + // Stop after ImGuiDir_None + if ((host_node && (host_node->MergedFlags & ImGuiDockNodeFlags_NoDockingSplit)) || g.IO.ConfigDockingNoSplit) + return; + } +} + +//----------------------------------------------------------------------------- +// Docking: ImGuiDockNode Tree manipulation functions +//----------------------------------------------------------------------------- +// - DockNodeTreeSplit() +// - DockNodeTreeMerge() +// - DockNodeTreeUpdatePosSize() +// - DockNodeTreeUpdateSplitterFindTouchingNode() +// - DockNodeTreeUpdateSplitter() +// - DockNodeTreeFindFallbackLeafNode() +// - DockNodeTreeFindNodeByPos() +//----------------------------------------------------------------------------- + +void ImGui::DockNodeTreeSplit(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiAxis split_axis, int split_inheritor_child_idx, float split_ratio, ImGuiDockNode* new_node) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(split_axis != ImGuiAxis_None); + + ImGuiDockNode* child_0 = (new_node && split_inheritor_child_idx != 0) ? new_node : DockContextAddNode(ctx, 0); + child_0->ParentNode = parent_node; + + ImGuiDockNode* child_1 = (new_node && split_inheritor_child_idx != 1) ? new_node : DockContextAddNode(ctx, 0); + child_1->ParentNode = parent_node; + + ImGuiDockNode* child_inheritor = (split_inheritor_child_idx == 0) ? child_0 : child_1; + DockNodeMoveChildNodes(child_inheritor, parent_node); + parent_node->ChildNodes[0] = child_0; + parent_node->ChildNodes[1] = child_1; + parent_node->ChildNodes[split_inheritor_child_idx]->VisibleWindow = parent_node->VisibleWindow; + parent_node->SplitAxis = split_axis; + parent_node->VisibleWindow = NULL; + parent_node->AuthorityForPos = parent_node->AuthorityForSize = ImGuiDataAuthority_DockNode; + + float size_avail = (parent_node->Size[split_axis] - g.Style.DockingSeparatorSize); + size_avail = ImMax(size_avail, g.Style.WindowMinSize[split_axis] * 2.0f); + IM_ASSERT(size_avail > 0.0f); // If you created a node manually with DockBuilderAddNode(), you need to also call DockBuilderSetNodeSize() before splitting. + child_0->SizeRef = child_1->SizeRef = parent_node->Size; + child_0->SizeRef[split_axis] = ImTrunc(size_avail * split_ratio); + child_1->SizeRef[split_axis] = ImTrunc(size_avail - child_0->SizeRef[split_axis]); + + DockNodeMoveWindows(parent_node->ChildNodes[split_inheritor_child_idx], parent_node); + DockSettingsRenameNodeReferences(parent_node->ID, parent_node->ChildNodes[split_inheritor_child_idx]->ID); + DockNodeUpdateHasCentralNodeChild(DockNodeGetRootNode(parent_node)); + DockNodeTreeUpdatePosSize(parent_node, parent_node->Pos, parent_node->Size); + + // Flags transfer (e.g. this is where we transfer the ImGuiDockNodeFlags_CentralNode property) + child_0->SharedFlags = parent_node->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_; + child_1->SharedFlags = parent_node->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_; + child_inheritor->LocalFlags = parent_node->LocalFlags & ImGuiDockNodeFlags_LocalFlagsTransferMask_; + parent_node->LocalFlags &= ~ImGuiDockNodeFlags_LocalFlagsTransferMask_; + child_0->UpdateMergedFlags(); + child_1->UpdateMergedFlags(); + parent_node->UpdateMergedFlags(); + if (child_inheritor->IsCentralNode()) + DockNodeGetRootNode(parent_node)->CentralNode = child_inheritor; +} + +void ImGui::DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiDockNode* merge_lead_child) +{ + // When called from DockContextProcessUndockNode() it is possible that one of the child is NULL. + ImGuiContext& g = *GImGui; + ImGuiDockNode* child_0 = parent_node->ChildNodes[0]; + ImGuiDockNode* child_1 = parent_node->ChildNodes[1]; + IM_ASSERT(child_0 || child_1); + IM_ASSERT(merge_lead_child == child_0 || merge_lead_child == child_1); + if ((child_0 && child_0->Windows.Size > 0) || (child_1 && child_1->Windows.Size > 0)) + { + IM_ASSERT(parent_node->TabBar == NULL); + IM_ASSERT(parent_node->Windows.Size == 0); + } + IMGUI_DEBUG_LOG_DOCKING("[docking] DockNodeTreeMerge: 0x%08X + 0x%08X back into parent 0x%08X\n", child_0 ? child_0->ID : 0, child_1 ? child_1->ID : 0, parent_node->ID); + + ImVec2 backup_last_explicit_size = parent_node->SizeRef; + DockNodeMoveChildNodes(parent_node, merge_lead_child); + if (child_0) + { + DockNodeMoveWindows(parent_node, child_0); // Generally only 1 of the 2 child node will have windows + DockSettingsRenameNodeReferences(child_0->ID, parent_node->ID); + } + if (child_1) + { + DockNodeMoveWindows(parent_node, child_1); + DockSettingsRenameNodeReferences(child_1->ID, parent_node->ID); + } + DockNodeApplyPosSizeToWindows(parent_node); + parent_node->AuthorityForPos = parent_node->AuthorityForSize = parent_node->AuthorityForViewport = ImGuiDataAuthority_Auto; + parent_node->VisibleWindow = merge_lead_child->VisibleWindow; + parent_node->SizeRef = backup_last_explicit_size; + + // Flags transfer + parent_node->LocalFlags &= ~ImGuiDockNodeFlags_LocalFlagsTransferMask_; // Preserve Dockspace flag + parent_node->LocalFlags |= (child_0 ? child_0->LocalFlags : 0) & ImGuiDockNodeFlags_LocalFlagsTransferMask_; + parent_node->LocalFlags |= (child_1 ? child_1->LocalFlags : 0) & ImGuiDockNodeFlags_LocalFlagsTransferMask_; + parent_node->LocalFlagsInWindows = (child_0 ? child_0->LocalFlagsInWindows : 0) | (child_1 ? child_1->LocalFlagsInWindows : 0); // FIXME: Would be more consistent to update from actual windows + parent_node->UpdateMergedFlags(); + + if (child_0) + { + ctx->DockContext.Nodes.SetVoidPtr(child_0->ID, NULL); + IM_DELETE(child_0); + } + if (child_1) + { + ctx->DockContext.Nodes.SetVoidPtr(child_1->ID, NULL); + IM_DELETE(child_1); + } +} + +// Update Pos/Size for a node hierarchy (don't affect child Windows yet) +// (Depth-first, Pre-Order) +void ImGui::DockNodeTreeUpdatePosSize(ImGuiDockNode* node, ImVec2 pos, ImVec2 size, ImGuiDockNode* only_write_to_single_node) +{ + // During the regular dock node update we write to all nodes. + // 'only_write_to_single_node' is only set when turning a node visible mid-frame and we need its size right-away. + ImGuiContext& g = *GImGui; + const bool write_to_node = only_write_to_single_node == NULL || only_write_to_single_node == node; + if (write_to_node) + { + node->Pos = pos; + node->Size = size; + } + + if (node->IsLeafNode()) + return; + + ImGuiDockNode* child_0 = node->ChildNodes[0]; + ImGuiDockNode* child_1 = node->ChildNodes[1]; + ImVec2 child_0_pos = pos, child_1_pos = pos; + ImVec2 child_0_size = size, child_1_size = size; + + const bool child_0_is_toward_single_node = (only_write_to_single_node != NULL && DockNodeIsInHierarchyOf(only_write_to_single_node, child_0)); + const bool child_1_is_toward_single_node = (only_write_to_single_node != NULL && DockNodeIsInHierarchyOf(only_write_to_single_node, child_1)); + const bool child_0_is_or_will_be_visible = child_0->IsVisible || child_0_is_toward_single_node; + const bool child_1_is_or_will_be_visible = child_1->IsVisible || child_1_is_toward_single_node; + + if (child_0_is_or_will_be_visible && child_1_is_or_will_be_visible) + { + const float spacing = g.Style.DockingSeparatorSize; + const ImGuiAxis axis = (ImGuiAxis)node->SplitAxis; + const float size_avail = ImMax(size[axis] - spacing, 0.0f); + + // Size allocation policy + // 1) The first 0..WindowMinSize[axis]*2 are allocated evenly to both windows. + const float size_min_each = ImTrunc(ImMin(size_avail, g.Style.WindowMinSize[axis] * 2.0f) * 0.5f); + + // FIXME: Blocks 2) and 3) are essentially doing nearly the same thing. + // Difference are: write-back to SizeRef; application of a minimum size; rounding before ImTrunc() + // Clarify and rework differences between Size & SizeRef and purpose of WantLockSizeOnce + + // 2) Process locked absolute size (during a splitter resize we preserve the child of nodes not touching the splitter edge) + if (child_0->WantLockSizeOnce && !child_1->WantLockSizeOnce) + { + child_0_size[axis] = child_0->SizeRef[axis] = ImMin(size_avail - 1.0f, child_0->Size[axis]); + child_1_size[axis] = child_1->SizeRef[axis] = (size_avail - child_0_size[axis]); + IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f); + } + else if (child_1->WantLockSizeOnce && !child_0->WantLockSizeOnce) + { + child_1_size[axis] = child_1->SizeRef[axis] = ImMin(size_avail - 1.0f, child_1->Size[axis]); + child_0_size[axis] = child_0->SizeRef[axis] = (size_avail - child_1_size[axis]); + IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f); + } + else if (child_0->WantLockSizeOnce && child_1->WantLockSizeOnce) + { + // FIXME-DOCK: We cannot honor the requested size, so apply ratio. + // Currently this path will only be taken if code programmatically sets WantLockSizeOnce + float split_ratio = child_0_size[axis] / (child_0_size[axis] + child_1_size[axis]); + child_0_size[axis] = child_0->SizeRef[axis] = ImTrunc(size_avail * split_ratio); + child_1_size[axis] = child_1->SizeRef[axis] = (size_avail - child_0_size[axis]); + IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f); + } + + // 3) If one window is the central node (~ use remaining space, should be made explicit!), use explicit size from the other, and remainder for the central node + else if (child_0->SizeRef[axis] != 0.0f && child_1->HasCentralNodeChild) + { + child_0_size[axis] = ImMin(size_avail - size_min_each, child_0->SizeRef[axis]); + child_1_size[axis] = (size_avail - child_0_size[axis]); + } + else if (child_1->SizeRef[axis] != 0.0f && child_0->HasCentralNodeChild) + { + child_1_size[axis] = ImMin(size_avail - size_min_each, child_1->SizeRef[axis]); + child_0_size[axis] = (size_avail - child_1_size[axis]); + } + else + { + // 4) Otherwise distribute according to the relative ratio of each SizeRef value + float split_ratio = child_0->SizeRef[axis] / (child_0->SizeRef[axis] + child_1->SizeRef[axis]); + child_0_size[axis] = ImMax(size_min_each, ImTrunc(size_avail * split_ratio + 0.5f)); + child_1_size[axis] = (size_avail - child_0_size[axis]); + } + + child_1_pos[axis] += spacing + child_0_size[axis]; + } + + if (only_write_to_single_node == NULL) + child_0->WantLockSizeOnce = child_1->WantLockSizeOnce = false; + + const bool child_0_recurse = only_write_to_single_node ? child_0_is_toward_single_node : child_0->IsVisible; + const bool child_1_recurse = only_write_to_single_node ? child_1_is_toward_single_node : child_1->IsVisible; + if (child_0_recurse) + DockNodeTreeUpdatePosSize(child_0, child_0_pos, child_0_size); + if (child_1_recurse) + DockNodeTreeUpdatePosSize(child_1, child_1_pos, child_1_size); +} + +static void DockNodeTreeUpdateSplitterFindTouchingNode(ImGuiDockNode* node, ImGuiAxis axis, int side, ImVector* touching_nodes) +{ + if (node->IsLeafNode()) + { + touching_nodes->push_back(node); + return; + } + if (node->ChildNodes[0]->IsVisible) + if (node->SplitAxis != axis || side == 0 || !node->ChildNodes[1]->IsVisible) + DockNodeTreeUpdateSplitterFindTouchingNode(node->ChildNodes[0], axis, side, touching_nodes); + if (node->ChildNodes[1]->IsVisible) + if (node->SplitAxis != axis || side == 1 || !node->ChildNodes[0]->IsVisible) + DockNodeTreeUpdateSplitterFindTouchingNode(node->ChildNodes[1], axis, side, touching_nodes); +} + +// (Depth-First, Pre-Order) +void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode* node) +{ + if (node->IsLeafNode()) + return; + + ImGuiContext& g = *GImGui; + + ImGuiDockNode* child_0 = node->ChildNodes[0]; + ImGuiDockNode* child_1 = node->ChildNodes[1]; + if (child_0->IsVisible && child_1->IsVisible) + { + // Bounding box of the splitter cover the space between both nodes (w = Spacing, h = Size[xy^1] for when splitting horizontally) + const ImGuiAxis axis = (ImGuiAxis)node->SplitAxis; + IM_ASSERT(axis != ImGuiAxis_None); + ImRect bb; + bb.Min = child_0->Pos; + bb.Max = child_1->Pos; + bb.Min[axis] += child_0->Size[axis]; + bb.Max[axis ^ 1] += child_1->Size[axis ^ 1]; + //if (g.IO.KeyCtrl) GetForegroundDrawList(g.CurrentWindow->Viewport)->AddRect(bb.Min, bb.Max, IM_COL32(255,0,255,255)); + + const ImGuiDockNodeFlags merged_flags = child_0->MergedFlags | child_1->MergedFlags; // Merged flags for BOTH childs + const ImGuiDockNodeFlags no_resize_axis_flag = (axis == ImGuiAxis_X) ? ImGuiDockNodeFlags_NoResizeX : ImGuiDockNodeFlags_NoResizeY; + if ((merged_flags & ImGuiDockNodeFlags_NoResize) || (merged_flags & no_resize_axis_flag)) + { + ImGuiWindow* window = g.CurrentWindow; + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator), g.Style.FrameRounding); + } + else + { + //bb.Min[axis] += 1; // Display a little inward so highlight doesn't connect with nearby tabs on the neighbor node. + //bb.Max[axis] -= 1; + PushID(node->ID); + + // Find resizing limits by gathering list of nodes that are touching the splitter line. + ImVector touching_nodes[2]; + float min_size = g.Style.WindowMinSize[axis]; + float resize_limits[2]; + resize_limits[0] = node->ChildNodes[0]->Pos[axis] + min_size; + resize_limits[1] = node->ChildNodes[1]->Pos[axis] + node->ChildNodes[1]->Size[axis] - min_size; + + ImGuiID splitter_id = GetID("##Splitter"); + if (g.ActiveId == splitter_id) // Only process when splitter is active + { + DockNodeTreeUpdateSplitterFindTouchingNode(child_0, axis, 1, &touching_nodes[0]); + DockNodeTreeUpdateSplitterFindTouchingNode(child_1, axis, 0, &touching_nodes[1]); + for (int touching_node_n = 0; touching_node_n < touching_nodes[0].Size; touching_node_n++) + resize_limits[0] = ImMax(resize_limits[0], touching_nodes[0][touching_node_n]->Rect().Min[axis] + min_size); + for (int touching_node_n = 0; touching_node_n < touching_nodes[1].Size; touching_node_n++) + resize_limits[1] = ImMin(resize_limits[1], touching_nodes[1][touching_node_n]->Rect().Max[axis] - min_size); + + // [DEBUG] Render touching nodes & limits + /* + ImDrawList* draw_list = node->HostWindow ? GetForegroundDrawList(node->HostWindow) : GetForegroundDrawList(GetMainViewport()); + for (int n = 0; n < 2; n++) + { + for (int touching_node_n = 0; touching_node_n < touching_nodes[n].Size; touching_node_n++) + draw_list->AddRect(touching_nodes[n][touching_node_n]->Pos, touching_nodes[n][touching_node_n]->Pos + touching_nodes[n][touching_node_n]->Size, IM_COL32(0, 255, 0, 255)); + if (axis == ImGuiAxis_X) + draw_list->AddLine(ImVec2(resize_limits[n], node->ChildNodes[n]->Pos.y), ImVec2(resize_limits[n], node->ChildNodes[n]->Pos.y + node->ChildNodes[n]->Size.y), IM_COL32(255, 0, 255, 255), 3.0f); + else + draw_list->AddLine(ImVec2(node->ChildNodes[n]->Pos.x, resize_limits[n]), ImVec2(node->ChildNodes[n]->Pos.x + node->ChildNodes[n]->Size.x, resize_limits[n]), IM_COL32(255, 0, 255, 255), 3.0f); + } + */ + } + + // Use a short delay before highlighting the splitter (and changing the mouse cursor) in order for regular mouse movement to not highlight many splitters + float cur_size_0 = child_0->Size[axis]; + float cur_size_1 = child_1->Size[axis]; + float min_size_0 = resize_limits[0] - child_0->Pos[axis]; + float min_size_1 = child_1->Pos[axis] + child_1->Size[axis] - resize_limits[1]; + ImU32 bg_col = GetColorU32(ImGuiCol_WindowBg); + if (SplitterBehavior(bb, GetID("##Splitter"), axis, &cur_size_0, &cur_size_1, min_size_0, min_size_1, WINDOWS_HOVER_PADDING, WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER, bg_col)) + { + if (touching_nodes[0].Size > 0 && touching_nodes[1].Size > 0) + { + child_0->Size[axis] = child_0->SizeRef[axis] = cur_size_0; + child_1->Pos[axis] -= cur_size_1 - child_1->Size[axis]; + child_1->Size[axis] = child_1->SizeRef[axis] = cur_size_1; + + // Lock the size of every node that is a sibling of the node we are touching + // This might be less desirable if we can merge sibling of a same axis into the same parental level. + for (int side_n = 0; side_n < 2; side_n++) + for (int touching_node_n = 0; touching_node_n < touching_nodes[side_n].Size; touching_node_n++) + { + ImGuiDockNode* touching_node = touching_nodes[side_n][touching_node_n]; + //ImDrawList* draw_list = node->HostWindow ? GetForegroundDrawList(node->HostWindow) : GetForegroundDrawList(GetMainViewport()); + //draw_list->AddRect(touching_node->Pos, touching_node->Pos + touching_node->Size, IM_COL32(255, 128, 0, 255)); + while (touching_node->ParentNode != node) + { + if (touching_node->ParentNode->SplitAxis == axis) + { + // Mark other node so its size will be preserved during the upcoming call to DockNodeTreeUpdatePosSize(). + ImGuiDockNode* node_to_preserve = touching_node->ParentNode->ChildNodes[side_n]; + node_to_preserve->WantLockSizeOnce = true; + //draw_list->AddRect(touching_node->Pos, touching_node->Rect().Max, IM_COL32(255, 0, 0, 255)); + //draw_list->AddRectFilled(node_to_preserve->Pos, node_to_preserve->Rect().Max, IM_COL32(0, 255, 0, 100)); + } + touching_node = touching_node->ParentNode; + } + } + + DockNodeTreeUpdatePosSize(child_0, child_0->Pos, child_0->Size); + DockNodeTreeUpdatePosSize(child_1, child_1->Pos, child_1->Size); + MarkIniSettingsDirty(); + } + } + PopID(); + } + } + + if (child_0->IsVisible) + DockNodeTreeUpdateSplitter(child_0); + if (child_1->IsVisible) + DockNodeTreeUpdateSplitter(child_1); +} + +ImGuiDockNode* ImGui::DockNodeTreeFindFallbackLeafNode(ImGuiDockNode* node) +{ + if (node->IsLeafNode()) + return node; + if (ImGuiDockNode* leaf_node = DockNodeTreeFindFallbackLeafNode(node->ChildNodes[0])) + return leaf_node; + if (ImGuiDockNode* leaf_node = DockNodeTreeFindFallbackLeafNode(node->ChildNodes[1])) + return leaf_node; + return NULL; +} + +ImGuiDockNode* ImGui::DockNodeTreeFindVisibleNodeByPos(ImGuiDockNode* node, ImVec2 pos) +{ + if (!node->IsVisible) + return NULL; + + const float dock_spacing = 0.0f;// g.Style.ItemInnerSpacing.x; // FIXME: Relation to DOCKING_SPLITTER_SIZE? + ImRect r(node->Pos, node->Pos + node->Size); + r.Expand(dock_spacing * 0.5f); + bool inside = r.Contains(pos); + if (!inside) + return NULL; + + if (node->IsLeafNode()) + return node; + if (ImGuiDockNode* hovered_node = DockNodeTreeFindVisibleNodeByPos(node->ChildNodes[0], pos)) + return hovered_node; + if (ImGuiDockNode* hovered_node = DockNodeTreeFindVisibleNodeByPos(node->ChildNodes[1], pos)) + return hovered_node; + + // This means we are hovering over the splitter/spacing of a parent node + return node; +} + +//----------------------------------------------------------------------------- +// Docking: Public Functions (SetWindowDock, DockSpace, DockSpaceOverViewport) +//----------------------------------------------------------------------------- +// - SetWindowDock() [Internal] +// - DockSpace() +// - DockSpaceOverViewport() +//----------------------------------------------------------------------------- + +// [Internal] Called via SetNextWindowDockID() +void ImGui::SetWindowDock(ImGuiWindow* window, ImGuiID dock_id, ImGuiCond cond) +{ + // Test condition (NB: bit 0 is always true) and clear flags for next time + if (cond && (window->SetWindowDockAllowFlags & cond) == 0) + return; + window->SetWindowDockAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + + if (window->DockId == dock_id) + return; + + // If the user attempt to set a dock id that is a split node, we'll dig within to find a suitable docking spot + ImGuiContext& g = *GImGui; + if (ImGuiDockNode* new_node = DockContextFindNodeByID(&g, dock_id)) + if (new_node->IsSplitNode()) + { + // Policy: Find central node or latest focused node. We first move back to our root node. + new_node = DockNodeGetRootNode(new_node); + if (new_node->CentralNode) + { + IM_ASSERT(new_node->CentralNode->IsCentralNode()); + dock_id = new_node->CentralNode->ID; + } + else + { + dock_id = new_node->LastFocusedNodeId; + } + } + + if (window->DockId == dock_id) + return; + + if (window->DockNode) + DockNodeRemoveWindow(window->DockNode, window, 0); + window->DockId = dock_id; +} + +// Create an explicit dockspace node within an existing window. Also expose dock node flags and creates a CentralNode by default. +// The Central Node is always displayed even when empty and shrink/extend according to the requested size of its neighbors. +// DockSpace() needs to be submitted _before_ any window they can host. If you use a dockspace, submit it early in your app. +// When ImGuiDockNodeFlags_KeepAliveOnly is set, nothing is submitted in the current window (function may be called from any location). +ImGuiID ImGui::DockSpace(ImGuiID dockspace_id, const ImVec2& size_arg, ImGuiDockNodeFlags flags, const ImGuiWindowClass* window_class) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindowRead(); + if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable)) + return 0; + + // Early out if parent window is hidden/collapsed + // This is faster but also DockNodeUpdateTabBar() relies on TabBarLayout() running (which won't if SkipItems=true) to set NextSelectedTabId = 0). See #2960. + // If for whichever reason this is causing problem we would need to ensure that DockNodeUpdateTabBar() ends up clearing NextSelectedTabId even if SkipItems=true. + if (window->SkipItems) + flags |= ImGuiDockNodeFlags_KeepAliveOnly; + if ((flags & ImGuiDockNodeFlags_KeepAliveOnly) == 0) + window = GetCurrentWindow(); // call to set window->WriteAccessed = true; + + IM_ASSERT((flags & ImGuiDockNodeFlags_DockSpace) == 0); // Flag is automatically set by DockSpace() as LocalFlags, not SharedFlags! + IM_ASSERT((flags & ImGuiDockNodeFlags_CentralNode) == 0); // Flag is automatically set by DockSpace() as LocalFlags, not SharedFlags! (#8145) + + IM_ASSERT(dockspace_id != 0); + ImGuiDockNode* node = DockContextFindNodeByID(&g, dockspace_id); + if (node == NULL) + { + IMGUI_DEBUG_LOG_DOCKING("[docking] DockSpace: dockspace node 0x%08X created\n", dockspace_id); + node = DockContextAddNode(&g, dockspace_id); + node->SetLocalFlags(ImGuiDockNodeFlags_CentralNode); + } + if (window_class && window_class->ClassId != node->WindowClass.ClassId) + IMGUI_DEBUG_LOG_DOCKING("[docking] DockSpace: dockspace node 0x%08X: setup WindowClass 0x%08X -> 0x%08X\n", dockspace_id, node->WindowClass.ClassId, window_class->ClassId); + node->SharedFlags = flags; + node->WindowClass = window_class ? *window_class : ImGuiWindowClass(); + + // When a DockSpace transitioned form implicit to explicit this may be called a second time + // It is possible that the node has already been claimed by a docked window which appeared before the DockSpace() node, so we overwrite IsDockSpace again. + if (node->LastFrameActive == g.FrameCount && !(flags & ImGuiDockNodeFlags_KeepAliveOnly)) + { + IM_ASSERT(node->IsDockSpace() == false && "Cannot call DockSpace() twice a frame with the same ID"); + node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_DockSpace); + return dockspace_id; + } + node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_DockSpace); + + // Keep alive mode, this is allow windows docked into this node so stay docked even if they are not visible + if (flags & ImGuiDockNodeFlags_KeepAliveOnly) + { + node->LastFrameAlive = g.FrameCount; + return dockspace_id; + } + + const ImVec2 content_avail = GetContentRegionAvail(); + ImVec2 size = ImTrunc(size_arg); + if (size.x <= 0.0f) + size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too much issues) + if (size.y <= 0.0f) + size.y = ImMax(content_avail.y + size.y, 4.0f); + IM_ASSERT(size.x > 0.0f && size.y > 0.0f); + + node->Pos = window->DC.CursorPos; + node->Size = node->SizeRef = size; + SetNextWindowPos(node->Pos); + SetNextWindowSize(node->Size); + g.NextWindowData.PosUndock = false; + + // FIXME-DOCK: Why do we need a child window to host a dockspace, could we host it in the existing window? + // FIXME-DOCK: What is the reason for not simply calling BeginChild()? (OK to have a reason but should be commented) + ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_DockNodeHost; + window_flags |= ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar; + window_flags |= ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; + window_flags |= ImGuiWindowFlags_NoBackground; + + char title[256]; + ImFormatString(title, IM_ARRAYSIZE(title), "%s/DockSpace_%08X", window->Name, dockspace_id); + + PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0.0f); + Begin(title, NULL, window_flags); + PopStyleVar(); + + ImGuiWindow* host_window = g.CurrentWindow; + DockNodeSetupHostWindow(node, host_window); + host_window->ChildId = window->GetID(title); + node->OnlyNodeWithWindows = NULL; + + IM_ASSERT(node->IsRootNode()); + + // We need to handle the rare case were a central node is missing. + // This can happen if the node was first created manually with DockBuilderAddNode() but _without_ the ImGuiDockNodeFlags_Dockspace. + // Doing it correctly would set the _CentralNode flags, which would then propagate according to subsequent split. + // It would also be ambiguous to attempt to assign a central node while there are split nodes, so we wait until there's a single node remaining. + // The specific sub-property of _CentralNode we are interested in recovering here is the "Don't delete when empty" property, + // as it doesn't make sense for an empty dockspace to not have this property. + if (node->IsLeafNode() && !node->IsCentralNode()) + node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_CentralNode); + + // Update the node + DockNodeUpdate(node); + + End(); + + ImRect bb(node->Pos, node->Pos + size); + ItemSize(size); + ItemAdd(bb, dockspace_id, NULL, ImGuiItemFlags_NoNav); // Not a nav point (could be, would need to draw the nav rect and replicate/refactor activation from BeginChild(), but seems like CTRL+Tab works better here?) + if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && IsWindowChildOf(g.HoveredWindow, host_window, false, true)) // To fullfill IsItemHovered(), similar to EndChild() + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; + + return dockspace_id; +} + +// Tips: Use with ImGuiDockNodeFlags_PassthruCentralNode! +// The limitation with this call is that your window won't have a local menu bar, but you can also use BeginMainMenuBar(). +// Even though we could pass window flags, it would also require the user to be able to call BeginMenuBar() somehow meaning we can't Begin/End in a single function. +// If you really want a menu bar inside the same window as the one hosting the dockspace, you will need to copy this code somewhere and tweak it. +ImGuiID ImGui::DockSpaceOverViewport(ImGuiID dockspace_id, const ImGuiViewport* viewport, ImGuiDockNodeFlags dockspace_flags, const ImGuiWindowClass* window_class) +{ + if (viewport == NULL) + viewport = GetMainViewport(); + + // Submit a window filling the entire viewport + SetNextWindowPos(viewport->WorkPos); + SetNextWindowSize(viewport->WorkSize); + SetNextWindowViewport(viewport->ID); + + ImGuiWindowFlags host_window_flags = 0; + host_window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking; + host_window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) + host_window_flags |= ImGuiWindowFlags_NoBackground; + + // FIXME-OPT: When using ImGuiDockNodeFlags_KeepAliveOnly with DockSpaceOverViewport() we might be able to spare submitting the window, + // since DockSpace() with that flag doesn't need a window. We'd only need to compute the default ID accordingly. + if (dockspace_flags & ImGuiDockNodeFlags_KeepAliveOnly) + host_window_flags |= ImGuiWindowFlags_NoMouseInputs; + + char label[32]; + ImFormatString(label, IM_ARRAYSIZE(label), "WindowOverViewport_%08X", viewport->ID); + + PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + Begin(label, NULL, host_window_flags); + PopStyleVar(3); + + // Submit the dockspace + if (dockspace_id == 0) + dockspace_id = GetID("DockSpace"); + DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags, window_class); + + End(); + + return dockspace_id; +} + +//----------------------------------------------------------------------------- +// Docking: Builder Functions +//----------------------------------------------------------------------------- +// Very early end-user API to manipulate dock nodes. +// Only available in imgui_internal.h. Expect this API to change/break! +// It is expected that those functions are all called _before_ the dockspace node submission. +//----------------------------------------------------------------------------- +// - DockBuilderDockWindow() +// - DockBuilderGetNode() +// - DockBuilderSetNodePos() +// - DockBuilderSetNodeSize() +// - DockBuilderAddNode() +// - DockBuilderRemoveNode() +// - DockBuilderRemoveNodeChildNodes() +// - DockBuilderRemoveNodeDockedWindows() +// - DockBuilderSplitNode() +// - DockBuilderCopyNodeRec() +// - DockBuilderCopyNode() +// - DockBuilderCopyWindowSettings() +// - DockBuilderCopyDockSpace() +// - DockBuilderFinish() +//----------------------------------------------------------------------------- + +void ImGui::DockBuilderDockWindow(const char* window_name, ImGuiID node_id) +{ + // We don't preserve relative order of multiple docked windows (by clearing DockOrder back to -1) + ImGuiContext& g = *GImGui; IM_UNUSED(g); + IMGUI_DEBUG_LOG_DOCKING("[docking] DockBuilderDockWindow '%s' to node 0x%08X\n", window_name, node_id); + ImGuiID window_id = ImHashStr(window_name); + if (ImGuiWindow* window = FindWindowByID(window_id)) + { + // Apply to created window + ImGuiID prev_node_id = window->DockId; + SetWindowDock(window, node_id, ImGuiCond_Always); + if (window->DockId != prev_node_id) + window->DockOrder = -1; + } + else + { + // Apply to settings + ImGuiWindowSettings* settings = FindWindowSettingsByID(window_id); + if (settings == NULL) + settings = CreateNewWindowSettings(window_name); + if (settings->DockId != node_id) + settings->DockOrder = -1; + settings->DockId = node_id; + } +} + +ImGuiDockNode* ImGui::DockBuilderGetNode(ImGuiID node_id) +{ + ImGuiContext& g = *GImGui; + return DockContextFindNodeByID(&g, node_id); +} + +void ImGui::DockBuilderSetNodePos(ImGuiID node_id, ImVec2 pos) +{ + ImGuiContext& g = *GImGui; + ImGuiDockNode* node = DockContextFindNodeByID(&g, node_id); + if (node == NULL) + return; + node->Pos = pos; + node->AuthorityForPos = ImGuiDataAuthority_DockNode; +} + +void ImGui::DockBuilderSetNodeSize(ImGuiID node_id, ImVec2 size) +{ + ImGuiContext& g = *GImGui; + ImGuiDockNode* node = DockContextFindNodeByID(&g, node_id); + if (node == NULL) + return; + IM_ASSERT(size.x > 0.0f && size.y > 0.0f); + node->Size = node->SizeRef = size; + node->AuthorityForSize = ImGuiDataAuthority_DockNode; +} + +// Make sure to use the ImGuiDockNodeFlags_DockSpace flag to create a dockspace node! Otherwise this will create a floating node! +// - Floating node: you can then call DockBuilderSetNodePos()/DockBuilderSetNodeSize() to position and size the floating node. +// - Dockspace node: calling DockBuilderSetNodePos() is unnecessary. +// - If you intend to split a node immediately after creation using DockBuilderSplitNode(), make sure to call DockBuilderSetNodeSize() beforehand! +// For various reason, the splitting code currently needs a base size otherwise space may not be allocated as precisely as you would expect. +// - Use (id == 0) to let the system allocate a node identifier. +// - Existing node with a same id will be removed. +ImGuiID ImGui::DockBuilderAddNode(ImGuiID node_id, ImGuiDockNodeFlags flags) +{ + ImGuiContext& g = *GImGui; IM_UNUSED(g); + IMGUI_DEBUG_LOG_DOCKING("[docking] DockBuilderAddNode 0x%08X flags=%08X\n", node_id, flags); + + if (node_id != 0) + DockBuilderRemoveNode(node_id); + + ImGuiDockNode* node = NULL; + if (flags & ImGuiDockNodeFlags_DockSpace) + { + DockSpace(node_id, ImVec2(0, 0), (flags & ~ImGuiDockNodeFlags_DockSpace) | ImGuiDockNodeFlags_KeepAliveOnly); + node = DockContextFindNodeByID(&g, node_id); + } + else + { + node = DockContextAddNode(&g, node_id); + node->SetLocalFlags(flags); + } + node->LastFrameAlive = g.FrameCount; // Set this otherwise BeginDocked will undock during the same frame. + return node->ID; +} + +void ImGui::DockBuilderRemoveNode(ImGuiID node_id) +{ + ImGuiContext& g = *GImGui; IM_UNUSED(g); + IMGUI_DEBUG_LOG_DOCKING("[docking] DockBuilderRemoveNode 0x%08X\n", node_id); + + ImGuiDockNode* node = DockContextFindNodeByID(&g, node_id); + if (node == NULL) + return; + DockBuilderRemoveNodeDockedWindows(node_id, true); + DockBuilderRemoveNodeChildNodes(node_id); + // Node may have moved or deleted if e.g. any merge happened + node = DockContextFindNodeByID(&g, node_id); + if (node == NULL) + return; + if (node->IsCentralNode() && node->ParentNode) + node->ParentNode->SetLocalFlags(node->ParentNode->LocalFlags | ImGuiDockNodeFlags_CentralNode); + DockContextRemoveNode(&g, node, true); +} + +// root_id = 0 to remove all, root_id != 0 to remove child of given node. +void ImGui::DockBuilderRemoveNodeChildNodes(ImGuiID root_id) +{ + ImGuiContext& g = *GImGui; + ImGuiDockContext* dc = &g.DockContext; + + ImGuiDockNode* root_node = root_id ? DockContextFindNodeByID(&g, root_id) : NULL; + if (root_id && root_node == NULL) + return; + bool has_central_node = false; + + ImGuiDataAuthority backup_root_node_authority_for_pos = root_node ? root_node->AuthorityForPos : ImGuiDataAuthority_Auto; + ImGuiDataAuthority backup_root_node_authority_for_size = root_node ? root_node->AuthorityForSize : ImGuiDataAuthority_Auto; + + // Process active windows + ImVector nodes_to_remove; + for (int n = 0; n < dc->Nodes.Data.Size; n++) + if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p) + { + bool want_removal = (root_id == 0) || (node->ID != root_id && DockNodeGetRootNode(node)->ID == root_id); + if (want_removal) + { + if (node->IsCentralNode()) + has_central_node = true; + if (root_id != 0) + DockContextQueueNotifyRemovedNode(&g, node); + if (root_node) + { + DockNodeMoveWindows(root_node, node); + DockSettingsRenameNodeReferences(node->ID, root_node->ID); + } + nodes_to_remove.push_back(node); + } + } + + // DockNodeMoveWindows->DockNodeAddWindow will normally set those when reaching two windows (which is only adequate during interactive merge) + // Make sure we don't lose our current pos/size. (FIXME-DOCK: Consider tidying up that code in DockNodeAddWindow instead) + if (root_node) + { + root_node->AuthorityForPos = backup_root_node_authority_for_pos; + root_node->AuthorityForSize = backup_root_node_authority_for_size; + } + + // Apply to settings + for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) + if (ImGuiID window_settings_dock_id = settings->DockId) + for (int n = 0; n < nodes_to_remove.Size; n++) + if (nodes_to_remove[n]->ID == window_settings_dock_id) + { + settings->DockId = root_id; + break; + } + + // Not really efficient, but easier to destroy a whole hierarchy considering DockContextRemoveNode is attempting to merge nodes + if (nodes_to_remove.Size > 1) + ImQsort(nodes_to_remove.Data, nodes_to_remove.Size, sizeof(ImGuiDockNode*), DockNodeComparerDepthMostFirst); + for (int n = 0; n < nodes_to_remove.Size; n++) + DockContextRemoveNode(&g, nodes_to_remove[n], false); + + if (root_id == 0) + { + dc->Nodes.Clear(); + dc->Requests.clear(); + } + else if (has_central_node) + { + root_node->CentralNode = root_node; + root_node->SetLocalFlags(root_node->LocalFlags | ImGuiDockNodeFlags_CentralNode); + } +} + +void ImGui::DockBuilderRemoveNodeDockedWindows(ImGuiID root_id, bool clear_settings_refs) +{ + // Clear references in settings + ImGuiContext& g = *GImGui; + if (clear_settings_refs) + { + for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) + { + bool want_removal = (root_id == 0) || (settings->DockId == root_id); + if (!want_removal && settings->DockId != 0) + if (ImGuiDockNode* node = DockContextFindNodeByID(&g, settings->DockId)) + if (DockNodeGetRootNode(node)->ID == root_id) + want_removal = true; + if (want_removal) + settings->DockId = 0; + } + } + + // Clear references in windows + for (int n = 0; n < g.Windows.Size; n++) + { + ImGuiWindow* window = g.Windows[n]; + bool want_removal = (root_id == 0) || (window->DockNode && DockNodeGetRootNode(window->DockNode)->ID == root_id) || (window->DockNodeAsHost && window->DockNodeAsHost->ID == root_id); + if (want_removal) + { + const ImGuiID backup_dock_id = window->DockId; + IM_UNUSED(backup_dock_id); + DockContextProcessUndockWindow(&g, window, clear_settings_refs); + if (!clear_settings_refs) + IM_ASSERT(window->DockId == backup_dock_id); + } + } +} + +// If 'out_id_at_dir' or 'out_id_at_opposite_dir' are non NULL, the function will write out the ID of the two new nodes created. +// Return value is ID of the node at the specified direction, so same as (*out_id_at_dir) if that pointer is set. +// FIXME-DOCK: We are not exposing nor using split_outer. +ImGuiID ImGui::DockBuilderSplitNode(ImGuiID id, ImGuiDir split_dir, float size_ratio_for_node_at_dir, ImGuiID* out_id_at_dir, ImGuiID* out_id_at_opposite_dir) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(split_dir != ImGuiDir_None); + IMGUI_DEBUG_LOG_DOCKING("[docking] DockBuilderSplitNode: node 0x%08X, split_dir %d\n", id, split_dir); + + ImGuiDockNode* node = DockContextFindNodeByID(&g, id); + if (node == NULL) + { + IM_ASSERT(node != NULL); + return 0; + } + + IM_ASSERT(!node->IsSplitNode()); // Assert if already Split + + ImGuiDockRequest req; + req.Type = ImGuiDockRequestType_Split; + req.DockTargetWindow = NULL; + req.DockTargetNode = node; + req.DockPayload = NULL; + req.DockSplitDir = split_dir; + req.DockSplitRatio = ImSaturate((split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? size_ratio_for_node_at_dir : 1.0f - size_ratio_for_node_at_dir); + req.DockSplitOuter = false; + DockContextProcessDock(&g, &req); + + ImGuiID id_at_dir = node->ChildNodes[(split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 0 : 1]->ID; + ImGuiID id_at_opposite_dir = node->ChildNodes[(split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 1 : 0]->ID; + if (out_id_at_dir) + *out_id_at_dir = id_at_dir; + if (out_id_at_opposite_dir) + *out_id_at_opposite_dir = id_at_opposite_dir; + return id_at_dir; +} + +static ImGuiDockNode* DockBuilderCopyNodeRec(ImGuiDockNode* src_node, ImGuiID dst_node_id_if_known, ImVector* out_node_remap_pairs) +{ + ImGuiContext& g = *GImGui; + ImGuiDockNode* dst_node = ImGui::DockContextAddNode(&g, dst_node_id_if_known); + dst_node->SharedFlags = src_node->SharedFlags; + dst_node->LocalFlags = src_node->LocalFlags; + dst_node->LocalFlagsInWindows = ImGuiDockNodeFlags_None; + dst_node->Pos = src_node->Pos; + dst_node->Size = src_node->Size; + dst_node->SizeRef = src_node->SizeRef; + dst_node->SplitAxis = src_node->SplitAxis; + dst_node->UpdateMergedFlags(); + + out_node_remap_pairs->push_back(src_node->ID); + out_node_remap_pairs->push_back(dst_node->ID); + + for (int child_n = 0; child_n < IM_ARRAYSIZE(src_node->ChildNodes); child_n++) + if (src_node->ChildNodes[child_n]) + { + dst_node->ChildNodes[child_n] = DockBuilderCopyNodeRec(src_node->ChildNodes[child_n], 0, out_node_remap_pairs); + dst_node->ChildNodes[child_n]->ParentNode = dst_node; + } + + IMGUI_DEBUG_LOG_DOCKING("[docking] Fork node %08X -> %08X (%d childs)\n", src_node->ID, dst_node->ID, dst_node->IsSplitNode() ? 2 : 0); + return dst_node; +} + +void ImGui::DockBuilderCopyNode(ImGuiID src_node_id, ImGuiID dst_node_id, ImVector* out_node_remap_pairs) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(src_node_id != 0); + IM_ASSERT(dst_node_id != 0); + IM_ASSERT(out_node_remap_pairs != NULL); + + DockBuilderRemoveNode(dst_node_id); + + ImGuiDockNode* src_node = DockContextFindNodeByID(&g, src_node_id); + IM_ASSERT(src_node != NULL); + + out_node_remap_pairs->clear(); + DockBuilderCopyNodeRec(src_node, dst_node_id, out_node_remap_pairs); + + IM_ASSERT((out_node_remap_pairs->Size % 2) == 0); +} + +void ImGui::DockBuilderCopyWindowSettings(const char* src_name, const char* dst_name) +{ + ImGuiWindow* src_window = FindWindowByName(src_name); + if (src_window == NULL) + return; + if (ImGuiWindow* dst_window = FindWindowByName(dst_name)) + { + dst_window->Pos = src_window->Pos; + dst_window->Size = src_window->Size; + dst_window->SizeFull = src_window->SizeFull; + dst_window->Collapsed = src_window->Collapsed; + } + else + { + ImGuiWindowSettings* dst_settings = FindWindowSettingsByID(ImHashStr(dst_name)); + if (!dst_settings) + dst_settings = CreateNewWindowSettings(dst_name); + ImVec2ih window_pos_2ih = ImVec2ih(src_window->Pos); + if (src_window->ViewportId != 0 && src_window->ViewportId != IMGUI_VIEWPORT_DEFAULT_ID) + { + dst_settings->ViewportPos = window_pos_2ih; + dst_settings->ViewportId = src_window->ViewportId; + dst_settings->Pos = ImVec2ih(0, 0); + } + else + { + dst_settings->Pos = window_pos_2ih; + } + dst_settings->Size = ImVec2ih(src_window->SizeFull); + dst_settings->Collapsed = src_window->Collapsed; + } +} + +// FIXME: Will probably want to change this signature, in particular how the window remapping pairs are passed. +void ImGui::DockBuilderCopyDockSpace(ImGuiID src_dockspace_id, ImGuiID dst_dockspace_id, ImVector* in_window_remap_pairs) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(src_dockspace_id != 0); + IM_ASSERT(dst_dockspace_id != 0); + IM_ASSERT(in_window_remap_pairs != NULL); + IM_ASSERT((in_window_remap_pairs->Size % 2) == 0); + + // Duplicate entire dock + // FIXME: When overwriting dst_dockspace_id, windows that aren't part of our dockspace window class but that are docked in a same node will be split apart, + // whereas we could attempt to at least keep them together in a new, same floating node. + ImVector node_remap_pairs; + DockBuilderCopyNode(src_dockspace_id, dst_dockspace_id, &node_remap_pairs); + + // Attempt to transition all the upcoming windows associated to dst_dockspace_id into the newly created hierarchy of dock nodes + // (The windows associated to src_dockspace_id are staying in place) + ImVector src_windows; + for (int remap_window_n = 0; remap_window_n < in_window_remap_pairs->Size; remap_window_n += 2) + { + const char* src_window_name = (*in_window_remap_pairs)[remap_window_n]; + const char* dst_window_name = (*in_window_remap_pairs)[remap_window_n + 1]; + ImGuiID src_window_id = ImHashStr(src_window_name); + src_windows.push_back(src_window_id); + + // Search in the remapping tables + ImGuiID src_dock_id = 0; + if (ImGuiWindow* src_window = FindWindowByID(src_window_id)) + src_dock_id = src_window->DockId; + else if (ImGuiWindowSettings* src_window_settings = FindWindowSettingsByID(src_window_id)) + src_dock_id = src_window_settings->DockId; + ImGuiID dst_dock_id = 0; + for (int dock_remap_n = 0; dock_remap_n < node_remap_pairs.Size; dock_remap_n += 2) + if (node_remap_pairs[dock_remap_n] == src_dock_id) + { + dst_dock_id = node_remap_pairs[dock_remap_n + 1]; + //node_remap_pairs[dock_remap_n] = node_remap_pairs[dock_remap_n + 1] = 0; // Clear + break; + } + + if (dst_dock_id != 0) + { + // Docked windows gets redocked into the new node hierarchy. + IMGUI_DEBUG_LOG_DOCKING("[docking] Remap live window '%s' 0x%08X -> '%s' 0x%08X\n", src_window_name, src_dock_id, dst_window_name, dst_dock_id); + DockBuilderDockWindow(dst_window_name, dst_dock_id); + } + else + { + // Floating windows gets their settings transferred (regardless of whether the new window already exist or not) + // When this is leading to a Copy and not a Move, we would get two overlapping floating windows. Could we possibly dock them together? + IMGUI_DEBUG_LOG_DOCKING("[docking] Remap window settings '%s' -> '%s'\n", src_window_name, dst_window_name); + DockBuilderCopyWindowSettings(src_window_name, dst_window_name); + } + } + + // Anything else in the source nodes of 'node_remap_pairs' are windows that are not included in the remapping list. + // Find those windows and move to them to the cloned dock node. This may be optional? + // Dock those are a second step as undocking would invalidate source dock nodes. + struct DockRemainingWindowTask { ImGuiWindow* Window; ImGuiID DockId; DockRemainingWindowTask(ImGuiWindow* window, ImGuiID dock_id) { Window = window; DockId = dock_id; } }; + ImVector dock_remaining_windows; + for (int dock_remap_n = 0; dock_remap_n < node_remap_pairs.Size; dock_remap_n += 2) + if (ImGuiID src_dock_id = node_remap_pairs[dock_remap_n]) + { + ImGuiID dst_dock_id = node_remap_pairs[dock_remap_n + 1]; + ImGuiDockNode* node = DockBuilderGetNode(src_dock_id); + for (int window_n = 0; window_n < node->Windows.Size; window_n++) + { + ImGuiWindow* window = node->Windows[window_n]; + if (src_windows.contains(window->ID)) + continue; + + // Docked windows gets redocked into the new node hierarchy. + IMGUI_DEBUG_LOG_DOCKING("[docking] Remap window '%s' %08X -> %08X\n", window->Name, src_dock_id, dst_dock_id); + dock_remaining_windows.push_back(DockRemainingWindowTask(window, dst_dock_id)); + } + } + for (const DockRemainingWindowTask& task : dock_remaining_windows) + DockBuilderDockWindow(task.Window->Name, task.DockId); +} + +// FIXME-DOCK: This is awkward because in series of split user is likely to loose access to its root node. +void ImGui::DockBuilderFinish(ImGuiID root_id) +{ + ImGuiContext& g = *GImGui; + //DockContextRebuild(&g); + DockContextBuildAddWindowsToNodes(&g, root_id); +} + +//----------------------------------------------------------------------------- +// Docking: Begin/End Support Functions (called from Begin/End) +//----------------------------------------------------------------------------- +// - GetWindowAlwaysWantOwnTabBar() +// - DockContextBindNodeToWindow() +// - BeginDocked() +// - BeginDockableDragDropSource() +// - BeginDockableDragDropTarget() +//----------------------------------------------------------------------------- + +bool ImGui::GetWindowAlwaysWantOwnTabBar(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (g.IO.ConfigDockingAlwaysTabBar || window->WindowClass.DockingAlwaysTabBar) + if ((window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDocking)) == 0) + if (!window->IsFallbackWindow) // We don't support AlwaysTabBar on the fallback/implicit window to avoid unused dock-node overhead/noise + return true; + return false; +} + +static ImGuiDockNode* ImGui::DockContextBindNodeToWindow(ImGuiContext* ctx, ImGuiWindow* window) +{ + ImGuiContext& g = *ctx; + ImGuiDockNode* node = DockContextFindNodeByID(ctx, window->DockId); + IM_ASSERT(window->DockNode == NULL); + + // We should not be docking into a split node (SetWindowDock should avoid this) + if (node && node->IsSplitNode()) + { + DockContextProcessUndockWindow(ctx, window); + return NULL; + } + + // Create node + if (node == NULL) + { + node = DockContextAddNode(ctx, window->DockId); + node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Window; + node->LastFrameAlive = g.FrameCount; + } + + // If the node just turned visible and is part of a hierarchy, it doesn't have a Size assigned by DockNodeTreeUpdatePosSize() yet, + // so we're forcing a Pos/Size update from the first ancestor that is already visible (often it will be the root node). + // If we don't do this, the window will be assigned a zero-size on its first frame, which won't ideally warm up the layout. + // This is a little wonky because we don't normally update the Pos/Size of visible node mid-frame. + if (!node->IsVisible) + { + ImGuiDockNode* ancestor_node = node; + while (!ancestor_node->IsVisible && ancestor_node->ParentNode) + ancestor_node = ancestor_node->ParentNode; + IM_ASSERT(ancestor_node->Size.x > 0.0f && ancestor_node->Size.y > 0.0f); + DockNodeUpdateHasCentralNodeChild(DockNodeGetRootNode(ancestor_node)); + DockNodeTreeUpdatePosSize(ancestor_node, ancestor_node->Pos, ancestor_node->Size, node); + } + + // Add window to node + bool node_was_visible = node->IsVisible; + DockNodeAddWindow(node, window, true); + node->IsVisible = node_was_visible; // Don't mark visible right away (so DockContextEndFrame() doesn't render it, maybe other side effects? will see) + IM_ASSERT(node == window->DockNode); + return node; +} + +static void StoreDockStyleForWindow(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++) + window->DockStyle.Colors[color_n] = ImGui::ColorConvertFloat4ToU32(g.Style.Colors[GWindowDockStyleColors[color_n]]); +} + +void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) +{ + ImGuiContext& g = *GImGui; + + // Clear fields ahead so most early-out paths don't have to do it + window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false; + + const bool auto_dock_node = GetWindowAlwaysWantOwnTabBar(window); + if (auto_dock_node) + { + if (window->DockId == 0) + { + IM_ASSERT(window->DockNode == NULL); + window->DockId = DockContextGenNodeID(&g); + } + } + else + { + // Calling SetNextWindowPos() undock windows by default (by setting PosUndock) + bool want_undock = false; + want_undock |= (window->Flags & ImGuiWindowFlags_NoDocking) != 0; + want_undock |= (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) && (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) && g.NextWindowData.PosUndock; + if (want_undock) + { + DockContextProcessUndockWindow(&g, window); + return; + } + } + + // Bind to our dock node + ImGuiDockNode* node = window->DockNode; + if (node != NULL) + IM_ASSERT(window->DockId == node->ID); + if (window->DockId != 0 && node == NULL) + { + node = DockContextBindNodeToWindow(&g, window); + if (node == NULL) + return; + } + +#if 0 + // Undock if the ImGuiDockNodeFlags_NoDockingInCentralNode got set + if (node->IsCentralNode && (node->Flags & ImGuiDockNodeFlags_NoDockingInCentralNode)) + { + DockContextProcessUndockWindow(ctx, window); + return; + } +#endif + + // Undock if our dockspace node disappeared + // Note how we are testing for LastFrameAlive and NOT LastFrameActive. A DockSpace node can be maintained alive while being inactive with ImGuiDockNodeFlags_KeepAliveOnly. + if (node->LastFrameAlive < g.FrameCount) + { + // If the window has been orphaned, transition the docknode to an implicit node processed in DockContextNewFrameUpdateDocking() + ImGuiDockNode* root_node = DockNodeGetRootNode(node); + if (root_node->LastFrameAlive < g.FrameCount) + DockContextProcessUndockWindow(&g, window); + else + window->DockIsActive = true; + return; + } + + // Store style overrides + StoreDockStyleForWindow(window); + + // Fast path return. It is common for windows to hold on a persistent DockId but be the only visible window, + // and never create neither a host window neither a tab bar. + // FIXME-DOCK: replace ->HostWindow NULL compare with something more explicit (~was initially intended as a first frame test) + if (node->HostWindow == NULL) + { + if (node->State == ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing) + window->DockIsActive = true; + if (node->Windows.Size > 1 && window->Appearing) // Only hide appearing window + DockNodeHideWindowDuringHostWindowCreation(window); + return; + } + + // We can have zero-sized nodes (e.g. children of a small-size dockspace) + IM_ASSERT(node->HostWindow); + IM_ASSERT(node->IsLeafNode()); + IM_ASSERT(node->Size.x >= 0.0f && node->Size.y >= 0.0f); + node->State = ImGuiDockNodeState_HostWindowVisible; + + // Undock if we are submitted earlier than the host window + if (!(node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly) && window->BeginOrderWithinContext < node->HostWindow->BeginOrderWithinContext) + { + DockContextProcessUndockWindow(&g, window); + return; + } + + // Position/Size window + SetNextWindowPos(node->Pos); + SetNextWindowSize(node->Size); + g.NextWindowData.PosUndock = false; // Cancel implicit undocking of SetNextWindowPos() + window->DockIsActive = true; + window->DockNodeIsVisible = true; + window->DockTabIsVisible = false; + if (node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly) + return; + + // When the window is selected we mark it as visible. + if (node->VisibleWindow == window) + window->DockTabIsVisible = true; + + // Update window flag + IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) == 0); + window->Flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoResize; + window->ChildFlags |= ImGuiChildFlags_AlwaysUseWindowPadding; + if (node->IsHiddenTabBar() || node->IsNoTabBar()) + window->Flags |= ImGuiWindowFlags_NoTitleBar; + else + window->Flags &= ~ImGuiWindowFlags_NoTitleBar; // Clear the NoTitleBar flag in case the user set it: confusingly enough we need a title bar height so we are correctly offset, but it won't be displayed! + + // Save new dock order only if the window has been visible once already + // This allows multiple windows to be created in the same frame and have their respective dock orders preserved. + if (node->TabBar && window->WasActive) + window->DockOrder = (short)DockNodeGetTabOrder(window); + + if ((node->WantCloseAll || node->WantCloseTabId == window->TabId) && p_open != NULL) + *p_open = false; + + // Update ChildId to allow returning from Child to Parent with Escape + ImGuiWindow* parent_window = window->DockNode->HostWindow; + window->ChildId = parent_window->GetID(window->Name); +} + +void ImGui::BeginDockableDragDropSource(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.ActiveId == window->MoveId); + IM_ASSERT(g.MovingWindow == window); + IM_ASSERT(g.CurrentWindow == window); + + // 0: Hold SHIFT to disable docking, 1: Hold SHIFT to enable docking. + if (g.IO.ConfigDockingWithShift != g.IO.KeyShift) + { + // When ConfigDockingWithShift is set, display a tooltip to increase UI affordance. + // We cannot set for HoveredWindowUnderMovingWindow != NULL here, as it is only valid/useful when drag and drop is already active + // (because of the 'is_mouse_dragging_with_an_expected_destination' logic in UpdateViewportsNewFrame() function) + IM_ASSERT(g.NextWindowData.Flags == 0); + if (g.IO.ConfigDockingWithShift && g.MouseStationaryTimer >= 1.0f && g.ActiveId >= 1.0f) + SetTooltip("%s", LocalizeGetMsg(ImGuiLocKey_DockingHoldShiftToDock)); + return; + } + + g.LastItemData.ID = window->MoveId; + window = window->RootWindowDockTree; + IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0); + bool is_drag_docking = (g.IO.ConfigDockingWithShift) || ImRect(0, 0, window->SizeFull.x, GetFrameHeight()).Contains(g.ActiveIdClickOffset); // FIXME-DOCKING: Need to make this stateful and explicit + ImGuiDragDropFlags drag_drop_flags = ImGuiDragDropFlags_SourceNoPreviewTooltip | ImGuiDragDropFlags_SourceNoHoldToOpenOthers | ImGuiDragDropFlags_PayloadAutoExpire | ImGuiDragDropFlags_PayloadNoCrossContext | ImGuiDragDropFlags_PayloadNoCrossProcess; + if (is_drag_docking && BeginDragDropSource(drag_drop_flags)) + { + SetDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW, &window, sizeof(window)); + EndDragDropSource(); + StoreDockStyleForWindow(window); // Store style overrides while dragging (even when not docked) because docking preview may need it. + } +} + +void ImGui::BeginDockableDragDropTarget(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + + //IM_ASSERT(window->RootWindowDockTree == window); // May also be a DockSpace + IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0); + if (!g.DragDropActive) + return; + //GetForegroundDrawList(window)->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255)); + if (!BeginDragDropTargetCustom(window->Rect(), window->ID)) + return; + + // Peek into the payload before calling AcceptDragDropPayload() so we can handle overlapping dock nodes with filtering + // (this is a little unusual pattern, normally most code would call AcceptDragDropPayload directly) + const ImGuiPayload* payload = &g.DragDropPayload; + if (!payload->IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) || !DockNodeIsDropAllowed(window, *(ImGuiWindow**)payload->Data)) + { + EndDragDropTarget(); + return; + } + + ImGuiWindow* payload_window = *(ImGuiWindow**)payload->Data; + if (AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW, ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect)) + { + // Select target node + // (Important: we cannot use g.HoveredDockNode here! Because each of our target node have filters based on payload, each candidate drop target will do its own evaluation) + bool dock_into_floating_window = false; + ImGuiDockNode* node = NULL; + if (window->DockNodeAsHost) + { + // Cannot assume that node will != NULL even though we passed the rectangle test: it depends on padding/spacing handled by DockNodeTreeFindVisibleNodeByPos(). + node = DockNodeTreeFindVisibleNodeByPos(window->DockNodeAsHost, g.IO.MousePos); + + // There is an edge case when docking into a dockspace which only has _inactive_ nodes (because none of the windows are active) + // In this case we need to fallback into any leaf mode, possibly the central node. + // FIXME-20181220: We should not have to test for IsLeafNode() here but we have another bug to fix first. + if (node && node->IsDockSpace() && node->IsRootNode()) + node = (node->CentralNode && node->IsLeafNode()) ? node->CentralNode : DockNodeTreeFindFallbackLeafNode(node); + } + else + { + if (window->DockNode) + node = window->DockNode; + else + dock_into_floating_window = true; // Dock into a regular window + } + + const ImRect explicit_target_rect = (node && node->TabBar && !node->IsHiddenTabBar() && !node->IsNoTabBar()) ? node->TabBar->BarRect : ImRect(window->Pos, window->Pos + ImVec2(window->Size.x, GetFrameHeight())); + const bool is_explicit_target = g.IO.ConfigDockingWithShift || IsMouseHoveringRect(explicit_target_rect.Min, explicit_target_rect.Max); + + // Preview docking request and find out split direction/ratio + //const bool do_preview = true; // Ignore testing for payload->IsPreview() which removes one frame of delay, but breaks overlapping drop targets within the same window. + const bool do_preview = payload->IsPreview() || payload->IsDelivery(); + if (do_preview && (node != NULL || dock_into_floating_window)) + { + // If we have a non-leaf node it means we are hovering the border of a parent node, in which case only outer markers will appear. + ImGuiDockPreviewData split_inner; + ImGuiDockPreviewData split_outer; + ImGuiDockPreviewData* split_data = &split_inner; + if (node && (node->ParentNode || node->IsCentralNode() || !node->IsLeafNode())) + if (ImGuiDockNode* root_node = DockNodeGetRootNode(node)) + { + DockNodePreviewDockSetup(window, root_node, payload_window, NULL, &split_outer, is_explicit_target, true); + if (split_outer.IsSplitDirExplicit) + split_data = &split_outer; + } + if (!node || node->IsLeafNode()) + DockNodePreviewDockSetup(window, node, payload_window, NULL, &split_inner, is_explicit_target, false); + if (split_data == &split_outer) + split_inner.IsDropAllowed = false; + + // Draw inner then outer, so that previewed tab (in inner data) will be behind the outer drop boxes + DockNodePreviewDockRender(window, node, payload_window, &split_inner); + DockNodePreviewDockRender(window, node, payload_window, &split_outer); + + // Queue docking request + if (split_data->IsDropAllowed && payload->IsDelivery()) + DockContextQueueDock(&g, window, split_data->SplitNode, payload_window, split_data->SplitDir, split_data->SplitRatio, split_data == &split_outer); + } + } + EndDragDropTarget(); +} + +//----------------------------------------------------------------------------- +// Docking: Settings +//----------------------------------------------------------------------------- +// - DockSettingsRenameNodeReferences() +// - DockSettingsRemoveNodeReferences() +// - DockSettingsFindNodeSettings() +// - DockSettingsHandler_ApplyAll() +// - DockSettingsHandler_ReadOpen() +// - DockSettingsHandler_ReadLine() +// - DockSettingsHandler_DockNodeToSettings() +// - DockSettingsHandler_WriteAll() +//----------------------------------------------------------------------------- + +static void ImGui::DockSettingsRenameNodeReferences(ImGuiID old_node_id, ImGuiID new_node_id) +{ + ImGuiContext& g = *GImGui; + IMGUI_DEBUG_LOG_DOCKING("[docking] DockSettingsRenameNodeReferences: from 0x%08X -> to 0x%08X\n", old_node_id, new_node_id); + for (int window_n = 0; window_n < g.Windows.Size; window_n++) + { + ImGuiWindow* window = g.Windows[window_n]; + if (window->DockId == old_node_id && window->DockNode == NULL) + window->DockId = new_node_id; + } + //// FIXME-OPT: We could remove this loop by storing the index in the map + for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) + if (settings->DockId == old_node_id) + settings->DockId = new_node_id; +} + +// Remove references stored in ImGuiWindowSettings to the given ImGuiDockNodeSettings +static void ImGui::DockSettingsRemoveNodeReferences(ImGuiID* node_ids, int node_ids_count) +{ + ImGuiContext& g = *GImGui; + int found = 0; + //// FIXME-OPT: We could remove this loop by storing the index in the map + for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) + for (int node_n = 0; node_n < node_ids_count; node_n++) + if (settings->DockId == node_ids[node_n]) + { + settings->DockId = 0; + settings->DockOrder = -1; + if (++found < node_ids_count) + break; + return; + } +} + +static ImGuiDockNodeSettings* ImGui::DockSettingsFindNodeSettings(ImGuiContext* ctx, ImGuiID id) +{ + // FIXME-OPT + ImGuiDockContext* dc = &ctx->DockContext; + for (int n = 0; n < dc->NodesSettings.Size; n++) + if (dc->NodesSettings[n].ID == id) + return &dc->NodesSettings[n]; + return NULL; +} + +// Clear settings data +static void ImGui::DockSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*) +{ + ImGuiDockContext* dc = &ctx->DockContext; + dc->NodesSettings.clear(); + DockContextClearNodes(ctx, 0, true); +} + +// Recreate nodes based on settings data +static void ImGui::DockSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*) +{ + // Prune settings at boot time only + ImGuiDockContext* dc = &ctx->DockContext; + if (ctx->Windows.Size == 0) + DockContextPruneUnusedSettingsNodes(ctx); + DockContextBuildNodesFromSettings(ctx, dc->NodesSettings.Data, dc->NodesSettings.Size); + DockContextBuildAddWindowsToNodes(ctx, 0); +} + +static void* ImGui::DockSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) +{ + if (strcmp(name, "Data") != 0) + return NULL; + return (void*)1; +} + +static void ImGui::DockSettingsHandler_ReadLine(ImGuiContext* ctx, ImGuiSettingsHandler*, void*, const char* line) +{ + char c = 0; + int x = 0, y = 0; + int r = 0; + + // Parsing, e.g. + // " DockNode ID=0x00000001 Pos=383,193 Size=201,322 Split=Y,0.506 " + // " DockNode ID=0x00000002 Parent=0x00000001 " + // Important: this code expect currently fields in a fixed order. + ImGuiDockNodeSettings node; + line = ImStrSkipBlank(line); + if (strncmp(line, "DockNode", 8) == 0) { line = ImStrSkipBlank(line + strlen("DockNode")); } + else if (strncmp(line, "DockSpace", 9) == 0) { line = ImStrSkipBlank(line + strlen("DockSpace")); node.Flags |= ImGuiDockNodeFlags_DockSpace; } + else return; + if (sscanf(line, "ID=0x%08X%n", &node.ID, &r) == 1) { line += r; } else return; + if (sscanf(line, " Parent=0x%08X%n", &node.ParentNodeId, &r) == 1) { line += r; if (node.ParentNodeId == 0) return; } + if (sscanf(line, " Window=0x%08X%n", &node.ParentWindowId, &r) ==1) { line += r; if (node.ParentWindowId == 0) return; } + if (node.ParentNodeId == 0) + { + if (sscanf(line, " Pos=%i,%i%n", &x, &y, &r) == 2) { line += r; node.Pos = ImVec2ih((short)x, (short)y); } else return; + if (sscanf(line, " Size=%i,%i%n", &x, &y, &r) == 2) { line += r; node.Size = ImVec2ih((short)x, (short)y); } else return; + } + else + { + if (sscanf(line, " SizeRef=%i,%i%n", &x, &y, &r) == 2) { line += r; node.SizeRef = ImVec2ih((short)x, (short)y); } + } + if (sscanf(line, " Split=%c%n", &c, &r) == 1) { line += r; if (c == 'X') node.SplitAxis = ImGuiAxis_X; else if (c == 'Y') node.SplitAxis = ImGuiAxis_Y; } + if (sscanf(line, " NoResize=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_NoResize; } + if (sscanf(line, " CentralNode=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_CentralNode; } + if (sscanf(line, " NoTabBar=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_NoTabBar; } + if (sscanf(line, " HiddenTabBar=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_HiddenTabBar; } + if (sscanf(line, " NoWindowMenuButton=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_NoWindowMenuButton; } + if (sscanf(line, " NoCloseButton=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_NoCloseButton; } + if (sscanf(line, " Selected=0x%08X%n", &node.SelectedTabId,&r) == 1) { line += r; } + if (node.ParentNodeId != 0) + if (ImGuiDockNodeSettings* parent_settings = DockSettingsFindNodeSettings(ctx, node.ParentNodeId)) + node.Depth = parent_settings->Depth + 1; + ctx->DockContext.NodesSettings.push_back(node); +} + +static void DockSettingsHandler_DockNodeToSettings(ImGuiDockContext* dc, ImGuiDockNode* node, int depth) +{ + ImGuiDockNodeSettings node_settings; + IM_ASSERT(depth < (1 << (sizeof(node_settings.Depth) << 3))); + node_settings.ID = node->ID; + node_settings.ParentNodeId = node->ParentNode ? node->ParentNode->ID : 0; + node_settings.ParentWindowId = (node->IsDockSpace() && node->HostWindow && node->HostWindow->ParentWindow) ? node->HostWindow->ParentWindow->ID : 0; + node_settings.SelectedTabId = node->SelectedTabId; + node_settings.SplitAxis = (signed char)(node->IsSplitNode() ? node->SplitAxis : ImGuiAxis_None); + node_settings.Depth = (char)depth; + node_settings.Flags = (node->LocalFlags & ImGuiDockNodeFlags_SavedFlagsMask_); + node_settings.Pos = ImVec2ih(node->Pos); + node_settings.Size = ImVec2ih(node->Size); + node_settings.SizeRef = ImVec2ih(node->SizeRef); + dc->NodesSettings.push_back(node_settings); + if (node->ChildNodes[0]) + DockSettingsHandler_DockNodeToSettings(dc, node->ChildNodes[0], depth + 1); + if (node->ChildNodes[1]) + DockSettingsHandler_DockNodeToSettings(dc, node->ChildNodes[1], depth + 1); +} + +static void ImGui::DockSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) +{ + ImGuiContext& g = *ctx; + ImGuiDockContext* dc = &ctx->DockContext; + if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable)) + return; + + // Gather settings data + // (unlike our windows settings, because nodes are always built we can do a full rewrite of the SettingsNode buffer) + dc->NodesSettings.resize(0); + dc->NodesSettings.reserve(dc->Nodes.Data.Size); + for (int n = 0; n < dc->Nodes.Data.Size; n++) + if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p) + if (node->IsRootNode()) + DockSettingsHandler_DockNodeToSettings(dc, node, 0); + + int max_depth = 0; + for (int node_n = 0; node_n < dc->NodesSettings.Size; node_n++) + max_depth = ImMax((int)dc->NodesSettings[node_n].Depth, max_depth); + + // Write to text buffer + buf->appendf("[%s][Data]\n", handler->TypeName); + for (int node_n = 0; node_n < dc->NodesSettings.Size; node_n++) + { + const int line_start_pos = buf->size(); (void)line_start_pos; + const ImGuiDockNodeSettings* node_settings = &dc->NodesSettings[node_n]; + buf->appendf("%*s%s%*s", node_settings->Depth * 2, "", (node_settings->Flags & ImGuiDockNodeFlags_DockSpace) ? "DockSpace" : "DockNode ", (max_depth - node_settings->Depth) * 2, ""); // Text align nodes to facilitate looking at .ini file + buf->appendf(" ID=0x%08X", node_settings->ID); + if (node_settings->ParentNodeId) + { + buf->appendf(" Parent=0x%08X SizeRef=%d,%d", node_settings->ParentNodeId, node_settings->SizeRef.x, node_settings->SizeRef.y); + } + else + { + if (node_settings->ParentWindowId) + buf->appendf(" Window=0x%08X", node_settings->ParentWindowId); + buf->appendf(" Pos=%d,%d Size=%d,%d", node_settings->Pos.x, node_settings->Pos.y, node_settings->Size.x, node_settings->Size.y); + } + if (node_settings->SplitAxis != ImGuiAxis_None) + buf->appendf(" Split=%c", (node_settings->SplitAxis == ImGuiAxis_X) ? 'X' : 'Y'); + if (node_settings->Flags & ImGuiDockNodeFlags_NoResize) + buf->appendf(" NoResize=1"); + if (node_settings->Flags & ImGuiDockNodeFlags_CentralNode) + buf->appendf(" CentralNode=1"); + if (node_settings->Flags & ImGuiDockNodeFlags_NoTabBar) + buf->appendf(" NoTabBar=1"); + if (node_settings->Flags & ImGuiDockNodeFlags_HiddenTabBar) + buf->appendf(" HiddenTabBar=1"); + if (node_settings->Flags & ImGuiDockNodeFlags_NoWindowMenuButton) + buf->appendf(" NoWindowMenuButton=1"); + if (node_settings->Flags & ImGuiDockNodeFlags_NoCloseButton) + buf->appendf(" NoCloseButton=1"); + if (node_settings->SelectedTabId) + buf->appendf(" Selected=0x%08X", node_settings->SelectedTabId); + + // [DEBUG] Include comments in the .ini file to ease debugging (this makes saving slower!) + if (g.IO.ConfigDebugIniSettings) + if (ImGuiDockNode* node = DockContextFindNodeByID(ctx, node_settings->ID)) + { + buf->appendf("%*s", ImMax(2, (line_start_pos + 92) - buf->size()), ""); // Align everything + if (node->IsDockSpace() && node->HostWindow && node->HostWindow->ParentWindow) + buf->appendf(" ; in '%s'", node->HostWindow->ParentWindow->Name); + // Iterate settings so we can give info about windows that didn't exist during the session. + int contains_window = 0; + for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) + if (settings->DockId == node_settings->ID) + { + if (contains_window++ == 0) + buf->appendf(" ; contains "); + buf->appendf("'%s' ", settings->GetName()); + } + } + + buf->appendf("\n"); + } + buf->appendf("\n"); +} + + +//----------------------------------------------------------------------------- +// [SECTION] PLATFORM DEPENDENT HELPERS +//----------------------------------------------------------------------------- +// - Default clipboard handlers +// - Default shell function handlers +// - Default IME handlers +//----------------------------------------------------------------------------- + +#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) + +#ifdef _MSC_VER +#pragma comment(lib, "user32") +#pragma comment(lib, "kernel32") +#endif + +// Win32 clipboard implementation +// We use g.ClipboardHandlerData for temporary storage to ensure it is freed on Shutdown() +static const char* Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext* ctx) +{ + ImGuiContext& g = *ctx; + g.ClipboardHandlerData.clear(); + if (!::OpenClipboard(NULL)) + return NULL; + HANDLE wbuf_handle = ::GetClipboardData(CF_UNICODETEXT); + if (wbuf_handle == NULL) + { + ::CloseClipboard(); + return NULL; + } + if (const WCHAR* wbuf_global = (const WCHAR*)::GlobalLock(wbuf_handle)) + { + int buf_len = ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, NULL, 0, NULL, NULL); + g.ClipboardHandlerData.resize(buf_len); + ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, g.ClipboardHandlerData.Data, buf_len, NULL, NULL); + } + ::GlobalUnlock(wbuf_handle); + ::CloseClipboard(); + return g.ClipboardHandlerData.Data; +} + +static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext*, const char* text) +{ + if (!::OpenClipboard(NULL)) + return; + const int wbuf_length = ::MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0); + HGLOBAL wbuf_handle = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(WCHAR)); + if (wbuf_handle == NULL) { ::CloseClipboard(); return; @@ -14204,7 +20538,7 @@ static PasteboardRef main_clipboard = 0; // OSX clipboard implementation // If you enable this you will need to add '-framework ApplicationServices' to your linker command-line! -static void SetClipboardTextFn_DefaultImpl(void*, const char* text) +static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext*, const char* text) { if (!main_clipboard) PasteboardCreate(kPasteboardClipboard, &main_clipboard); @@ -14217,9 +20551,9 @@ static void SetClipboardTextFn_DefaultImpl(void*, const char* text) } } -static const char* GetClipboardTextFn_DefaultImpl(void* user_data_ctx) +static const char* Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext* ctx) { - ImGuiContext& g = *(ImGuiContext*)user_data_ctx; + ImGuiContext& g = *ctx; if (!main_clipboard) PasteboardCreate(kPasteboardClipboard, &main_clipboard); PasteboardSynchronize(main_clipboard); @@ -14253,15 +20587,15 @@ static const char* GetClipboardTextFn_DefaultImpl(void* user_data_ctx) #else // Local Dear ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers. -static const char* GetClipboardTextFn_DefaultImpl(void* user_data_ctx) +static const char* Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext* ctx) { - ImGuiContext& g = *(ImGuiContext*)user_data_ctx; + ImGuiContext& g = *ctx; return g.ClipboardHandlerData.empty() ? NULL : g.ClipboardHandlerData.begin(); } -static void SetClipboardTextFn_DefaultImpl(void* user_data_ctx, const char* text) +static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext* ctx, const char* text) { - ImGuiContext& g = *(ImGuiContext*)user_data_ctx; + ImGuiContext& g = *ctx; g.ClipboardHandlerData.clear(); const char* text_end = text + strlen(text); g.ClipboardHandlerData.resize((int)(text_end - text) + 1); @@ -14269,8 +20603,62 @@ static void SetClipboardTextFn_DefaultImpl(void* user_data_ctx, const char* text g.ClipboardHandlerData[(int)(text_end - text)] = 0; } +#endif // Default clipboard handlers + +//----------------------------------------------------------------------------- + +#ifndef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS +#if defined(__APPLE__) && TARGET_OS_IPHONE +#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS #endif +#if defined(_WIN32) && defined(IMGUI_DISABLE_WIN32_FUNCTIONS) +#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS +#endif +#endif + +#ifndef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS +#ifdef _WIN32 +#include // ShellExecuteA() +#ifdef _MSC_VER +#pragma comment(lib, "shell32") +#endif +static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext*, const char* path) +{ + return (INT_PTR)::ShellExecuteA(NULL, "open", path, NULL, NULL, SW_SHOWDEFAULT) > 32; +} +#else +#include +#include +static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext*, const char* path) +{ +#if defined(__APPLE__) + const char* args[] { "open", "--", path, NULL }; +#else + const char* args[] { "xdg-open", path, NULL }; +#endif + pid_t pid = fork(); + if (pid < 0) + return false; + if (!pid) + { + execvp(args[0], const_cast(args)); + exit(-1); + } + else + { + int status; + waitpid(pid, &status, 0); + return WEXITSTATUS(status) == 0; + } +} +#endif +#else +static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext*, const char*) { return false; } +#endif // Default shell handlers + +//----------------------------------------------------------------------------- + // Win32 API IME support (for Asian languages, etc.) #if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) @@ -14279,7 +20667,7 @@ static void SetClipboardTextFn_DefaultImpl(void* user_data_ctx, const char* text #pragma comment(lib, "imm32") #endif -static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport* viewport, ImGuiPlatformImeData* data) +static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport* viewport, ImGuiPlatformImeData* data) { // Notify OS Input Method Editor of text input position HWND hwnd = (HWND)viewport->PlatformHandleRaw; @@ -14290,14 +20678,14 @@ static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport* viewport, ImGuiPlatf if (HIMC himc = ::ImmGetContext(hwnd)) { COMPOSITIONFORM composition_form = {}; - composition_form.ptCurrentPos.x = (LONG)data->InputPos.x; - composition_form.ptCurrentPos.y = (LONG)data->InputPos.y; + composition_form.ptCurrentPos.x = (LONG)(data->InputPos.x - viewport->Pos.x); + composition_form.ptCurrentPos.y = (LONG)(data->InputPos.y - viewport->Pos.y); composition_form.dwStyle = CFS_FORCE_POSITION; ::ImmSetCompositionWindow(himc, &composition_form); CANDIDATEFORM candidate_form = {}; candidate_form.dwStyle = CFS_CANDIDATEPOS; - candidate_form.ptCurrentPos.x = (LONG)data->InputPos.x; - candidate_form.ptCurrentPos.y = (LONG)data->InputPos.y; + candidate_form.ptCurrentPos.x = (LONG)(data->InputPos.x - viewport->Pos.x); + candidate_form.ptCurrentPos.y = (LONG)(data->InputPos.y - viewport->Pos.y); ::ImmSetCandidateWindow(himc, &candidate_form); ::ImmReleaseContext(hwnd, himc); } @@ -14305,9 +20693,9 @@ static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport* viewport, ImGuiPlatf #else -static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport*, ImGuiPlatformImeData*) {} +static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport*, ImGuiPlatformImeData*) {} -#endif +#endif // Default IME handlers //----------------------------------------------------------------------------- // [SECTION] METRICS/DEBUGGER WINDOW @@ -14319,6 +20707,7 @@ static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport*, ImGuiPlatformImeDat // - ShowFontAtlas() [Internal] // - ShowMetricsWindow() // - DebugNodeColumns() [Internal] +// - DebugNodeDockNode() [Internal] // - DebugNodeDrawList() [Internal] // - DebugNodeDrawCmdShowMeshAndBoundingBox() [Internal] // - DebugNodeFont() [Internal] @@ -14341,12 +20730,14 @@ void ImGui::DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* ImVec2 scale = bb.GetSize() / viewport->Size; ImVec2 off = bb.Min - viewport->Pos * scale; - float alpha_mul = 1.0f; + float alpha_mul = (viewport->Flags & ImGuiViewportFlags_IsMinimized) ? 0.30f : 1.00f; window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border, alpha_mul * 0.40f)); for (ImGuiWindow* thumb_window : g.Windows) { if (!thumb_window->WasActive || (thumb_window->Flags & ImGuiWindowFlags_ChildWindow)) continue; + if (thumb_window->Viewport != viewport) + continue; ImRect thumb_r = thumb_window->Rect(); ImRect title_r = thumb_window->TitleBarRect(); @@ -14370,10 +20761,19 @@ static void RenderViewportsThumbnails() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + // Draw monitor and calculate their boundaries float SCALE = 1.0f / 8.0f; - ImRect bb_full(g.Viewports[0]->Pos, g.Viewports[0]->Pos + g.Viewports[0]->Size); + ImRect bb_full(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); + for (ImGuiPlatformMonitor& monitor : g.PlatformIO.Monitors) + bb_full.Add(ImRect(monitor.MainPos, monitor.MainPos + monitor.MainSize)); ImVec2 p = window->DC.CursorPos; ImVec2 off = p - bb_full.Min * SCALE; + for (ImGuiPlatformMonitor& monitor : g.PlatformIO.Monitors) + { + ImRect monitor_draw_bb(off + (monitor.MainPos) * SCALE, off + (monitor.MainPos + monitor.MainSize) * SCALE); + window->DrawList->AddRect(monitor_draw_bb.Min, monitor_draw_bb.Max, (g.DebugMetricsConfig.HighlightMonitorIdx == g.PlatformIO.Monitors.index_from_ptr(&monitor)) ? IM_COL32(255, 255, 0, 255) : ImGui::GetColorU32(ImGuiCol_Border), 4.0f); + window->DrawList->AddRectFilled(monitor_draw_bb.Min, monitor_draw_bb.Max, ImGui::GetColorU32(ImGuiCol_Border, 0.10f), 4.0f); + } // Draw viewports for (ImGuiViewportP* viewport : g.Viewports) @@ -14384,6 +20784,13 @@ static void RenderViewportsThumbnails() ImGui::Dummy(bb_full.GetSize() * SCALE); } +static int IMGUI_CDECL ViewportComparerByLastFocusedStampCount(const void* lhs, const void* rhs) +{ + const ImGuiViewportP* a = *(const ImGuiViewportP* const*)lhs; + const ImGuiViewportP* b = *(const ImGuiViewportP* const*)rhs; + return b->LastFocusedStampCount - a->LastFocusedStampCount; +} + // Draw an arbitrary US keyboard layout to visualize translated keys void ImGui::DebugRenderKeyboardPreview(ImDrawList* draw_list) { @@ -14493,12 +20900,23 @@ void ImGui::UpdateDebugToolFlashStyleColor() ImGuiContext& g = *GImGui; if (g.DebugFlashStyleColorTime <= 0.0f) return; - ColorConvertHSVtoRGB(cosf(g.DebugFlashStyleColorTime * 6.0f) * 0.5f + 0.5f, 0.5f, 0.5f, g.Style.Colors[g.DebugFlashStyleColorIdx].x, g.Style.Colors[g.DebugFlashStyleColorIdx].y, g.Style.Colors[g.DebugFlashStyleColorIdx].z); + ColorConvertHSVtoRGB(ImCos(g.DebugFlashStyleColorTime * 6.0f) * 0.5f + 0.5f, 0.5f, 0.5f, g.Style.Colors[g.DebugFlashStyleColorIdx].x, g.Style.Colors[g.DebugFlashStyleColorIdx].y, g.Style.Colors[g.DebugFlashStyleColorIdx].z); g.Style.Colors[g.DebugFlashStyleColorIdx].w = 1.0f; if ((g.DebugFlashStyleColorTime -= g.IO.DeltaTime) <= 0.0f) DebugFlashStyleColorStop(); } +static const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTextureID tex_id) +{ + union { void* ptr; int integer; } tex_id_opaque; + memcpy(&tex_id_opaque, &tex_id, ImMin(sizeof(void*), sizeof(tex_id))); + if (sizeof(tex_id) >= sizeof(void*)) + ImFormatString(buf, buf_size, "0x%p", tex_id_opaque.ptr); + else + ImFormatString(buf, buf_size, "0x%04X", tex_id_opaque.integer); + return buf; +} + // Avoid naming collision with imgui_demo.cpp's HelpMarker() for unity builds. static void MetricsHelpMarker(const char* desc) { @@ -14553,7 +20971,12 @@ void ImGui::ShowMetricsWindow(bool* p_open) DebugBreakClearData(); // Basic info - Text("Dear ImGui %s", GetVersion()); + Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); + if (g.ContextName[0] != 0) + { + SameLine(); + Text("(Context Name: \"%s\")", g.ContextName); + } Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); Text("%d vertices, %d indices (%d triangles)", io.MetricsRenderVertices, io.MetricsRenderIndices, io.MetricsRenderIndices / 3); Text("%d visible windows, %d current allocations", io.MetricsRenderWindows, g.DebugAllocInfo.TotalAllocCount - g.DebugAllocInfo.TotalFreeCount); @@ -14746,14 +21169,38 @@ void ImGui::ShowMetricsWindow(bool* p_open) Checkbox("Show ImDrawCmd mesh when hovering", &cfg->ShowDrawCmdMesh); Checkbox("Show ImDrawCmd bounding boxes when hovering", &cfg->ShowDrawCmdBoundingBoxes); for (ImGuiViewportP* viewport : g.Viewports) + { + bool viewport_has_drawlist = false; for (ImDrawList* draw_list : viewport->DrawDataP.CmdLists) + { + if (!viewport_has_drawlist) + Text("Active DrawLists in Viewport #%d, ID: 0x%08X", viewport->Idx, viewport->ID); + viewport_has_drawlist = true; DebugNodeDrawList(NULL, viewport, draw_list, "DrawList"); + } + } TreePop(); } // Viewports if (TreeNode("Viewports", "Viewports (%d)", g.Viewports.Size)) { + cfg->HighlightMonitorIdx = -1; + bool open = TreeNode("Monitors", "Monitors (%d)", g.PlatformIO.Monitors.Size); + SameLine(); + MetricsHelpMarker("Dear ImGui uses monitor data:\n- to query DPI settings on a per monitor basis\n- to position popup/tooltips so they don't straddle monitors."); + if (open) + { + for (int i = 0; i < g.PlatformIO.Monitors.Size; i++) + { + DebugNodePlatformMonitor(&g.PlatformIO.Monitors[i], "Monitor", i); + if (IsItemHovered()) + cfg->HighlightMonitorIdx = i; + } + DebugNodePlatformMonitor(&g.FallbackMonitor, "Fallback", 0); + TreePop(); + } + SetNextItemOpen(true, ImGuiCond_Once); if (TreeNode("Windows Minimap")) { @@ -14762,6 +21209,26 @@ void ImGui::ShowMetricsWindow(bool* p_open) } cfg->HighlightViewportID = 0; + BulletText("MouseViewport: 0x%08X (UserHovered 0x%08X, LastHovered 0x%08X)", g.MouseViewport ? g.MouseViewport->ID : 0, g.IO.MouseHoveredViewport, g.MouseLastHoveredViewport ? g.MouseLastHoveredViewport->ID : 0); + if (TreeNode("Inferred Z order (front-to-back)")) + { + static ImVector viewports; + viewports.resize(g.Viewports.Size); + memcpy(viewports.Data, g.Viewports.Data, g.Viewports.size_in_bytes()); + if (viewports.Size > 1) + ImQsort(viewports.Data, viewports.Size, sizeof(ImGuiViewport*), ViewportComparerByLastFocusedStampCount); + for (ImGuiViewportP* viewport : viewports) + { + BulletText("Viewport #%d, ID: 0x%08X, LastFocused = %08d, PlatformFocused = %s, Window: \"%s\"", + viewport->Idx, viewport->ID, viewport->LastFocusedStampCount, + (g.PlatformIO.Platform_GetWindowFocus && viewport->PlatformWindowCreated) ? (g.PlatformIO.Platform_GetWindowFocus(viewport) ? "1" : "0") : "N/A", + viewport->Window ? viewport->Window->Name : "N/A"); + if (IsItemHovered()) + cfg->HighlightViewportID = viewport->ID; + } + TreePop(); + } + for (ImGuiViewportP* viewport : g.Viewports) DebugNodeViewport(viewport); TreePop(); @@ -14825,10 +21292,32 @@ void ImGui::ShowMetricsWindow(bool* p_open) TreePop(); } + // Details for MultiSelect + if (TreeNode("MultiSelect", "MultiSelect (%d)", g.MultiSelectStorage.GetAliveCount())) + { + ImGuiBoxSelectState* bs = &g.BoxSelectState; + BulletText("BoxSelect ID=0x%08X, Starting = %d, Active %d", bs->ID, bs->IsStarting, bs->IsActive); + for (int n = 0; n < g.MultiSelectStorage.GetMapSize(); n++) + if (ImGuiMultiSelectState* state = g.MultiSelectStorage.TryGetMapData(n)) + DebugNodeMultiSelectState(state); + TreePop(); + } + // Details for Docking #ifdef IMGUI_HAS_DOCK if (TreeNode("Docking")) { + static bool root_nodes_only = true; + ImGuiDockContext* dc = &g.DockContext; + Checkbox("List root nodes", &root_nodes_only); + Checkbox("Ctrl shows window dock info", &cfg->ShowDockingNodes); + if (SmallButton("Clear nodes")) { DockContextClearNodes(&g, 0, true); } + SameLine(); + if (SmallButton("Rebuild all")) { dc->WantFullRebuild = true; } + for (int n = 0; n < dc->Nodes.Data.Size; n++) + if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p) + if (!root_nodes_only || node->IsRootNode()) + DebugNodeDockNode(node, "Node"); TreePop(); } #endif // #ifdef IMGUI_HAS_DOCK @@ -14872,6 +21361,29 @@ void ImGui::ShowMetricsWindow(bool* p_open) } #ifdef IMGUI_HAS_DOCK + if (TreeNode("SettingsDocking", "Settings packed data: Docking")) + { + ImGuiDockContext* dc = &g.DockContext; + Text("In SettingsWindows:"); + for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) + if (settings->DockId != 0) + BulletText("Window '%s' -> DockId %08X DockOrder=%d", settings->GetName(), settings->DockId, settings->DockOrder); + Text("In SettingsNodes:"); + for (int n = 0; n < dc->NodesSettings.Size; n++) + { + ImGuiDockNodeSettings* settings = &dc->NodesSettings[n]; + const char* selected_tab_name = NULL; + if (settings->SelectedTabId) + { + if (ImGuiWindow* window = FindWindowByID(settings->SelectedTabId)) + selected_tab_name = window->Name; + else if (ImGuiWindowSettings* window_settings = FindWindowSettingsByID(settings->SelectedTabId)) + selected_tab_name = window_settings->GetName(); + } + BulletText("Node %08X, Parent %08X, SelectedTab %08X ('%s')", settings->ID, settings->ParentNodeId, settings->SelectedTabId, selected_tab_name ? selected_tab_name : settings->SelectedTabId ? "N/A" : ""); + } + TreePop(); + } #endif // #ifdef IMGUI_HAS_DOCK if (TreeNode("SettingsIniData", "Settings unpacked data (.ini): %d bytes", g.SettingsIniData.size())) @@ -14893,7 +21405,12 @@ void ImGui::ShowMetricsWindow(bool* p_open) for (int n = buf_size - 1; n >= 0; n--) { ImGuiDebugAllocEntry* entry = &info->LastEntriesBuf[(info->LastEntriesIdx - n + buf_size) % buf_size]; - BulletText("Frame %06d: %+3d ( %2d malloc, %2d free )%s", entry->FrameCount, entry->AllocCount - entry->FreeCount, entry->AllocCount, entry->FreeCount, (n == 0) ? " (most recent)" : ""); + BulletText("Frame %06d: %+3d ( %2d alloc, %2d free )", entry->FrameCount, entry->AllocCount - entry->FreeCount, entry->AllocCount, entry->FreeCount); + if (n == 0) + { + SameLine(); + Text("<- %d frames ago", g.FrameCount - entry->FrameCount); + } } TreePop(); } @@ -14902,18 +21419,11 @@ void ImGui::ShowMetricsWindow(bool* p_open) { Text("KEYBOARD/GAMEPAD/MOUSE KEYS"); { - // We iterate both legacy native range and named ImGuiKey ranges, which is a little odd but this allows displaying the data for old/new backends. // User code should never have to go through such hoops! You can generally iterate between ImGuiKey_NamedKey_BEGIN and ImGuiKey_NamedKey_END. Indent(); -#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO - struct funcs { static bool IsLegacyNativeDupe(ImGuiKey) { return false; } }; -#else - struct funcs { static bool IsLegacyNativeDupe(ImGuiKey key) { return key >= 0 && key < 512 && GetIO().KeyMap[key] != -1; } }; // Hide Native<>ImGuiKey duplicates when both exists in the array - //Text("Legacy raw:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key++) { if (io.KeysDown[key]) { SameLine(); Text("\"%s\" %d", GetKeyName(key), key); } } -#endif - Text("Keys down:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !IsKeyDown(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); SameLine(); Text("(%.02f)", GetKeyData(key)->DownDuration); } - Text("Keys pressed:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !IsKeyPressed(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); } - Text("Keys released:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !IsKeyReleased(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); } + Text("Keys down:"); for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { if (!IsKeyDown(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); SameLine(); Text("(%.02f)", GetKeyData(key)->DownDuration); } + Text("Keys pressed:"); for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { if (!IsKeyPressed(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); } + Text("Keys released:"); for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { if (!IsKeyReleased(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); } Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : ""); Text("Chars queue:"); for (int i = 0; i < io.InputQueueCharacters.Size; i++) { ImWchar c = io.InputQueueCharacters[i]; SameLine(); Text("\'%c\' (0x%04X)", (c > ' ' && c <= 255) ? (char)c : '?', c); } // FIXME: We should convert 'c' to UTF-8 here but the functions are not public. DebugRenderKeyboardPreview(GetWindowDrawList()); @@ -15000,9 +21510,11 @@ void ImGui::ShowMetricsWindow(bool* p_open) Text("WINDOWING"); Indent(); Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL"); - Text("HoveredWindow->Root: '%s'", g.HoveredWindow ? g.HoveredWindow->RootWindow->Name : "NULL"); + Text("HoveredWindow->Root: '%s'", g.HoveredWindow ? g.HoveredWindow->RootWindowDockTree->Name : "NULL"); Text("HoveredWindowUnderMovingWindow: '%s'", g.HoveredWindowUnderMovingWindow ? g.HoveredWindowUnderMovingWindow->Name : "NULL"); + Text("HoveredDockNode: 0x%08X", g.DebugHoveredDockNode ? g.DebugHoveredDockNode->ID : 0); Text("MovingWindow: '%s'", g.MovingWindow ? g.MovingWindow->Name : "NULL"); + Text("MouseViewport: 0x%08X (UserHovered 0x%08X, LastHovered 0x%08X)", g.MouseViewport->ID, g.IO.MouseHoveredViewport, g.MouseLastHoveredViewport ? g.MouseLastHoveredViewport->ID : 0); Unindent(); Text("ITEMS"); @@ -15027,7 +21539,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible); Text("NavActivateId/DownId/PressedId: %08X/%08X/%08X", g.NavActivateId, g.NavActivateDownId, g.NavActivatePressedId); Text("NavActivateFlags: %04X", g.NavActivateFlags); - Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover); + Text("NavCursorVisible: %d, NavHighlightItemUnderNav: %d", g.NavCursorVisible, g.NavHighlightItemUnderNav); Text("NavFocusScopeId = 0x%08X", g.NavFocusScopeId); Text("NavFocusRoute[] = "); for (int path_n = g.NavFocusRoute.Size - 1; path_n >= 0; path_n--) @@ -15096,8 +21608,21 @@ void ImGui::ShowMetricsWindow(bool* p_open) #ifdef IMGUI_HAS_DOCK // Overlay: Display Docking info - if (show_docking_nodes && g.IO.KeyCtrl) - { + if (cfg->ShowDockingNodes && g.IO.KeyCtrl && g.DebugHoveredDockNode) + { + char buf[64] = ""; + char* p = buf; + ImGuiDockNode* node = g.DebugHoveredDockNode; + ImDrawList* overlay_draw_list = node->HostWindow ? GetForegroundDrawList(node->HostWindow) : GetForegroundDrawList(GetMainViewport()); + p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "DockId: %X%s\n", node->ID, node->IsCentralNode() ? " *CentralNode*" : ""); + p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "WindowClass: %08X\n", node->WindowClass.ClassId); + p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "Size: (%.0f, %.0f)\n", node->Size.x, node->Size.y); + p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "SizeRef: (%.0f, %.0f)\n", node->SizeRef.x, node->SizeRef.y); + int depth = DockNodeGetDepth(node); + overlay_draw_list->AddRect(node->Pos + ImVec2(3, 3) * (float)depth, node->Pos + node->Size - ImVec2(3, 3) * (float)depth, IM_COL32(200, 100, 100, 255)); + ImVec2 pos = node->Pos + ImVec2(3, 3) * (float)depth; + overlay_draw_list->AddRectFilled(pos - ImVec2(1, 1), pos + CalcTextSize(buf) + ImVec2(1, 1), IM_COL32(200, 100, 100, 255)); + overlay_draw_list->AddText(NULL, 0.0f, pos, IM_COL32(255, 255, 255, 255), buf); } #endif // #ifdef IMGUI_HAS_DOCK @@ -15154,7 +21679,7 @@ bool ImGui::DebugBreakButton(const char* label, const char* description_of_locat ColorConvertRGBtoHSV(col4f.x, col4f.y, col4f.z, hsv.x, hsv.y, hsv.z); ColorConvertHSVtoRGB(hsv.x + 0.20f, hsv.y, hsv.z, col4f.x, col4f.y, col4f.z); - RenderNavHighlight(bb, id); + RenderNavCursor(bb, id); RenderFrame(bb.Min, bb.Max, GetColorU32(col4f), true, g.Style.FrameRounding); RenderTextClipped(bb.Min, bb.Max, label, NULL, &label_size, g.Style.ButtonTextAlign, &bb); @@ -15173,21 +21698,98 @@ void ImGui::DebugNodeColumns(ImGuiOldColumns* columns) TreePop(); } -static void FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTextureID tex_id) +static void DebugNodeDockNodeFlags(ImGuiDockNodeFlags* p_flags, const char* label, bool enabled) { - union { void* ptr; int integer; } tex_id_opaque; - memcpy(&tex_id_opaque, &tex_id, ImMin(sizeof(void*), sizeof(tex_id))); - if (sizeof(tex_id) >= sizeof(void*)) - ImFormatString(buf, buf_size, "0x%p", tex_id_opaque.ptr); + using namespace ImGui; + PushID(label); + PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f)); + Text("%s:", label); + if (!enabled) + BeginDisabled(); + CheckboxFlags("NoResize", p_flags, ImGuiDockNodeFlags_NoResize); + CheckboxFlags("NoResizeX", p_flags, ImGuiDockNodeFlags_NoResizeX); + CheckboxFlags("NoResizeY",p_flags, ImGuiDockNodeFlags_NoResizeY); + CheckboxFlags("NoTabBar", p_flags, ImGuiDockNodeFlags_NoTabBar); + CheckboxFlags("HiddenTabBar", p_flags, ImGuiDockNodeFlags_HiddenTabBar); + CheckboxFlags("NoWindowMenuButton", p_flags, ImGuiDockNodeFlags_NoWindowMenuButton); + CheckboxFlags("NoCloseButton", p_flags, ImGuiDockNodeFlags_NoCloseButton); + CheckboxFlags("DockedWindowsInFocusRoute", p_flags, ImGuiDockNodeFlags_DockedWindowsInFocusRoute); + CheckboxFlags("NoDocking", p_flags, ImGuiDockNodeFlags_NoDocking); // Multiple flags + CheckboxFlags("NoDockingSplit", p_flags, ImGuiDockNodeFlags_NoDockingSplit); + CheckboxFlags("NoDockingSplitOther", p_flags, ImGuiDockNodeFlags_NoDockingSplitOther); + CheckboxFlags("NoDockingOver", p_flags, ImGuiDockNodeFlags_NoDockingOverMe); + CheckboxFlags("NoDockingOverOther", p_flags, ImGuiDockNodeFlags_NoDockingOverOther); + CheckboxFlags("NoDockingOverEmpty", p_flags, ImGuiDockNodeFlags_NoDockingOverEmpty); + CheckboxFlags("NoUndocking", p_flags, ImGuiDockNodeFlags_NoUndocking); + if (!enabled) + EndDisabled(); + PopStyleVar(); + PopID(); +} + +// [DEBUG] Display contents of ImDockNode +void ImGui::DebugNodeDockNode(ImGuiDockNode* node, const char* label) +{ + ImGuiContext& g = *GImGui; + const bool is_alive = (g.FrameCount - node->LastFrameAlive < 2); // Submitted with ImGuiDockNodeFlags_KeepAliveOnly + const bool is_active = (g.FrameCount - node->LastFrameActive < 2); // Submitted + if (!is_alive) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); } + bool open; + ImGuiTreeNodeFlags tree_node_flags = node->IsFocused ? ImGuiTreeNodeFlags_Selected : ImGuiTreeNodeFlags_None; + if (node->Windows.Size > 0) + open = TreeNodeEx((void*)(intptr_t)node->ID, tree_node_flags, "%s 0x%04X%s: %d windows (vis: '%s')", label, node->ID, node->IsVisible ? "" : " (hidden)", node->Windows.Size, node->VisibleWindow ? node->VisibleWindow->Name : "NULL"); else - ImFormatString(buf, buf_size, "0x%04X", tex_id_opaque.integer); + open = TreeNodeEx((void*)(intptr_t)node->ID, tree_node_flags, "%s 0x%04X%s: %s (vis: '%s')", label, node->ID, node->IsVisible ? "" : " (hidden)", (node->SplitAxis == ImGuiAxis_X) ? "horizontal split" : (node->SplitAxis == ImGuiAxis_Y) ? "vertical split" : "empty", node->VisibleWindow ? node->VisibleWindow->Name : "NULL"); + if (!is_alive) { PopStyleColor(); } + if (is_active && IsItemHovered()) + if (ImGuiWindow* window = node->HostWindow ? node->HostWindow : node->VisibleWindow) + GetForegroundDrawList(window)->AddRect(node->Pos, node->Pos + node->Size, IM_COL32(255, 255, 0, 255)); + if (open) + { + IM_ASSERT(node->ChildNodes[0] == NULL || node->ChildNodes[0]->ParentNode == node); + IM_ASSERT(node->ChildNodes[1] == NULL || node->ChildNodes[1]->ParentNode == node); + BulletText("Pos (%.0f,%.0f), Size (%.0f, %.0f) Ref (%.0f, %.0f)", + node->Pos.x, node->Pos.y, node->Size.x, node->Size.y, node->SizeRef.x, node->SizeRef.y); + DebugNodeWindow(node->HostWindow, "HostWindow"); + DebugNodeWindow(node->VisibleWindow, "VisibleWindow"); + BulletText("SelectedTabID: 0x%08X, LastFocusedNodeID: 0x%08X", node->SelectedTabId, node->LastFocusedNodeId); + BulletText("Misc:%s%s%s%s%s%s%s", + node->IsDockSpace() ? " IsDockSpace" : "", + node->IsCentralNode() ? " IsCentralNode" : "", + is_alive ? " IsAlive" : "", is_active ? " IsActive" : "", node->IsFocused ? " IsFocused" : "", + node->WantLockSizeOnce ? " WantLockSizeOnce" : "", + node->HasCentralNodeChild ? " HasCentralNodeChild" : ""); + if (TreeNode("flags", "Flags Merged: 0x%04X, Local: 0x%04X, InWindows: 0x%04X, Shared: 0x%04X", node->MergedFlags, node->LocalFlags, node->LocalFlagsInWindows, node->SharedFlags)) + { + if (BeginTable("flags", 4)) + { + TableNextColumn(); DebugNodeDockNodeFlags(&node->MergedFlags, "MergedFlags", false); + TableNextColumn(); DebugNodeDockNodeFlags(&node->LocalFlags, "LocalFlags", true); + TableNextColumn(); DebugNodeDockNodeFlags(&node->LocalFlagsInWindows, "LocalFlagsInWindows", false); + TableNextColumn(); DebugNodeDockNodeFlags(&node->SharedFlags, "SharedFlags", true); + EndTable(); + } + TreePop(); + } + if (node->ParentNode) + DebugNodeDockNode(node->ParentNode, "ParentNode"); + if (node->ChildNodes[0]) + DebugNodeDockNode(node->ChildNodes[0], "Child[0]"); + if (node->ChildNodes[1]) + DebugNodeDockNode(node->ChildNodes[1], "Child[1]"); + if (node->TabBar) + DebugNodeTabBar(node->TabBar, "TabBar"); + DebugNodeWindowsList(&node->Windows, "Windows"); + + TreePop(); + } } // [DEBUG] Display contents of ImDrawList +// Note that both 'window' and 'viewport' may be NULL here. Viewport is generally null of destroyed popups which previously owned a viewport. void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, const ImDrawList* draw_list, const char* label) { ImGuiContext& g = *GImGui; - IM_UNUSED(viewport); // Used in docking branch ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; int cmd_count = draw_list->CmdBuffer.Size; if (cmd_count > 0 && draw_list->CmdBuffer.back().ElemCount == 0 && draw_list->CmdBuffer.back().UserCallback == NULL) @@ -15202,7 +21804,7 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con return; } - ImDrawList* fg_draw_list = GetForegroundDrawList(window); // Render additional visuals into the top-most draw list + ImDrawList* fg_draw_list = viewport ? GetForegroundDrawList(viewport) : NULL; // Render additional visuals into the top-most draw list if (window && IsItemHovered() && fg_draw_list) fg_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255)); if (!node_open) @@ -15416,7 +22018,10 @@ void ImGui::DebugNodeStorage(ImGuiStorage* storage, const char* label) if (!TreeNode(label, "%s: %d entries, %d bytes", label, storage->Data.Size, storage->Data.size_in_bytes())) return; for (const ImGuiStoragePair& p : storage->Data) + { BulletText("Key 0x%08X Value { i: %d }", p.key, p.val_i); // Important: we currently don't store a type, real value may not be integer. + DebugLocateItemOnHover(p.key); + } TreePop(); } @@ -15465,25 +22070,46 @@ void ImGui::DebugNodeViewport(ImGuiViewportP* viewport) { ImGuiContext& g = *GImGui; SetNextItemOpen(true, ImGuiCond_Once); - bool open = TreeNode("viewport0", "Viewport #%d", 0); + bool open = TreeNode((void*)(intptr_t)viewport->ID, "Viewport #%d, ID: 0x%08X, Parent: 0x%08X, Window: \"%s\"", viewport->Idx, viewport->ID, viewport->ParentViewportId, viewport->Window ? viewport->Window->Name : "N/A"); if (IsItemHovered()) g.DebugMetricsConfig.HighlightViewportID = viewport->ID; if (open) { ImGuiWindowFlags flags = viewport->Flags; - BulletText("Main Pos: (%.0f,%.0f), Size: (%.0f,%.0f)\nWorkArea Offset Left: %.0f Top: %.0f, Right: %.0f, Bottom: %.0f", + BulletText("Main Pos: (%.0f,%.0f), Size: (%.0f,%.0f)\nWorkArea Inset Left: %.0f Top: %.0f, Right: %.0f, Bottom: %.0f\nMonitor: %d, DpiScale: %.0f%%", viewport->Pos.x, viewport->Pos.y, viewport->Size.x, viewport->Size.y, - viewport->WorkOffsetMin.x, viewport->WorkOffsetMin.y, viewport->WorkOffsetMax.x, viewport->WorkOffsetMax.y); - BulletText("Flags: 0x%04X =%s%s%s", viewport->Flags, - (flags & ImGuiViewportFlags_IsPlatformWindow) ? " IsPlatformWindow" : "", + viewport->WorkInsetMin.x, viewport->WorkInsetMin.y, viewport->WorkInsetMax.x, viewport->WorkInsetMax.y, + viewport->PlatformMonitor, viewport->DpiScale * 100.0f); + if (viewport->Idx > 0) { SameLine(); if (SmallButton("Reset Pos")) { viewport->Pos = ImVec2(200, 200); viewport->UpdateWorkRect(); if (viewport->Window) viewport->Window->Pos = viewport->Pos; } } + BulletText("Flags: 0x%04X =%s%s%s%s%s%s%s%s%s%s%s%s%s", viewport->Flags, + //(flags & ImGuiViewportFlags_IsPlatformWindow) ? " IsPlatformWindow" : "", // Omitting because it is the standard (flags & ImGuiViewportFlags_IsPlatformMonitor) ? " IsPlatformMonitor" : "", - (flags & ImGuiViewportFlags_OwnedByApp) ? " OwnedByApp" : ""); + (flags & ImGuiViewportFlags_IsMinimized) ? " IsMinimized" : "", + (flags & ImGuiViewportFlags_IsFocused) ? " IsFocused" : "", + (flags & ImGuiViewportFlags_OwnedByApp) ? " OwnedByApp" : "", + (flags & ImGuiViewportFlags_NoDecoration) ? " NoDecoration" : "", + (flags & ImGuiViewportFlags_NoTaskBarIcon) ? " NoTaskBarIcon" : "", + (flags & ImGuiViewportFlags_NoFocusOnAppearing) ? " NoFocusOnAppearing" : "", + (flags & ImGuiViewportFlags_NoFocusOnClick) ? " NoFocusOnClick" : "", + (flags & ImGuiViewportFlags_NoInputs) ? " NoInputs" : "", + (flags & ImGuiViewportFlags_NoRendererClear) ? " NoRendererClear" : "", + (flags & ImGuiViewportFlags_NoAutoMerge) ? " NoAutoMerge" : "", + (flags & ImGuiViewportFlags_TopMost) ? " TopMost" : "", + (flags & ImGuiViewportFlags_CanHostOtherWindows) ? " CanHostOtherWindows" : ""); for (ImDrawList* draw_list : viewport->DrawDataP.CmdLists) DebugNodeDrawList(NULL, viewport, draw_list, "DrawList"); TreePop(); } } +void ImGui::DebugNodePlatformMonitor(ImGuiPlatformMonitor* monitor, const char* label, int idx) +{ + BulletText("%s %d: DPI %.0f%%\n MainMin (%.0f,%.0f), MainMax (%.0f,%.0f), MainSize (%.0f,%.0f)\n WorkMin (%.0f,%.0f), WorkMax (%.0f,%.0f), WorkSize (%.0f,%.0f)", + label, idx, monitor->DpiScale * 100.0f, + monitor->MainPos.x, monitor->MainPos.y, monitor->MainPos.x + monitor->MainSize.x, monitor->MainPos.y + monitor->MainSize.y, monitor->MainSize.x, monitor->MainSize.y, + monitor->WorkPos.x, monitor->WorkPos.y, monitor->WorkPos.x + monitor->WorkSize.x, monitor->WorkPos.y + monitor->WorkSize.y, monitor->WorkSize.x, monitor->WorkSize.y); +} + void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) { if (window == NULL) @@ -15516,13 +22142,20 @@ void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) (flags & ImGuiWindowFlags_ChildWindow) ? "Child " : "", (flags & ImGuiWindowFlags_Tooltip) ? "Tooltip " : "", (flags & ImGuiWindowFlags_Popup) ? "Popup " : "", (flags & ImGuiWindowFlags_Modal) ? "Modal " : "", (flags & ImGuiWindowFlags_ChildMenu) ? "ChildMenu " : "", (flags & ImGuiWindowFlags_NoSavedSettings) ? "NoSavedSettings " : "", (flags & ImGuiWindowFlags_NoMouseInputs)? "NoMouseInputs":"", (flags & ImGuiWindowFlags_NoNavInputs) ? "NoNavInputs" : "", (flags & ImGuiWindowFlags_AlwaysAutoResize) ? "AlwaysAutoResize" : ""); + if (flags & ImGuiWindowFlags_ChildWindow) + BulletText("ChildFlags: 0x%08X (%s%s%s%s..)", window->ChildFlags, + (window->ChildFlags & ImGuiChildFlags_Borders) ? "Borders " : "", + (window->ChildFlags & ImGuiChildFlags_ResizeX) ? "ResizeX " : "", + (window->ChildFlags & ImGuiChildFlags_ResizeY) ? "ResizeY " : "", + (window->ChildFlags & ImGuiChildFlags_NavFlattened) ? "NavFlattened " : ""); + BulletText("WindowClassId: 0x%08X", window->WindowClass.ClassId); BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f) Scrollbar:%s%s", window->Scroll.x, window->ScrollMax.x, window->Scroll.y, window->ScrollMax.y, window->ScrollbarX ? "X" : "", window->ScrollbarY ? "Y" : ""); BulletText("Active: %d/%d, WriteAccessed: %d, BeginOrderWithinContext: %d", window->Active, window->WasActive, window->WriteAccessed, (window->Active || window->WasActive) ? window->BeginOrderWithinContext : -1); BulletText("Appearing: %d, Hidden: %d (CanSkip %d Cannot %d), SkipItems: %d", window->Appearing, window->Hidden, window->HiddenFramesCanSkipItems, window->HiddenFramesCannotSkipItems, window->SkipItems); for (int layer = 0; layer < ImGuiNavLayer_COUNT; layer++) { ImRect r = window->NavRectRel[layer]; - if (r.Min.x >= r.Max.y && r.Min.y >= r.Max.y) + if (r.Min.x >= r.Max.x && r.Min.y >= r.Max.y) BulletText("NavLastIds[%d]: 0x%08X", layer, window->NavLastIds[layer]); else BulletText("NavLastIds[%d]: 0x%08X at +(%.1f,%.1f)(%.1f,%.1f)", layer, window->NavLastIds[layer], r.Min.x, r.Min.y, r.Max.x, r.Max.y); @@ -15532,7 +22165,15 @@ void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) for (int layer = 0; layer < ImGuiNavLayer_COUNT; layer++) BulletText("NavPreferredScoringPosRel[%d] = {%.1f,%.1f)", layer, (pr[layer].x == FLT_MAX ? -99999.0f : pr[layer].x), (pr[layer].y == FLT_MAX ? -99999.0f : pr[layer].y)); // Display as 99999.0f so it looks neater. BulletText("NavLayersActiveMask: %X, NavLastChildNavWindow: %s", window->DC.NavLayersActiveMask, window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL"); + + BulletText("Viewport: %d%s, ViewportId: 0x%08X, ViewportPos: (%.1f,%.1f)", window->Viewport ? window->Viewport->Idx : -1, window->ViewportOwned ? " (Owned)" : "", window->ViewportId, window->ViewportPos.x, window->ViewportPos.y); + BulletText("ViewportMonitor: %d", window->Viewport ? window->Viewport->PlatformMonitor : -1); + BulletText("DockId: 0x%04X, DockOrder: %d, Act: %d, Vis: %d", window->DockId, window->DockOrder, window->DockIsActive, window->DockTabIsVisible); + if (window->DockNode || window->DockNodeAsHost) + DebugNodeDockNode(window->DockNodeAsHost ? window->DockNodeAsHost : window->DockNode, window->DockNodeAsHost ? "DockNodeAsHost" : "DockNode"); + if (window->RootWindow != window) { DebugNodeWindow(window->RootWindow, "RootWindow"); } + if (window->RootWindowDockTree != window->RootWindow) { DebugNodeWindow(window->RootWindowDockTree, "RootWindowDockTree"); } if (window->ParentWindow != NULL) { DebugNodeWindow(window->ParentWindow, "ParentWindow"); } if (window->ParentWindowForFocusRoute != NULL) { DebugNodeWindow(window->ParentWindowForFocusRoute, "ParentWindowForFocusRoute"); } if (window->DC.ChildWindows.Size > 0) { DebugNodeWindowsList(&window->DC.ChildWindows, "ChildWindows"); } @@ -15603,7 +22244,10 @@ void ImGui::DebugLogV(const char* fmt, va_list args) { ImGuiContext& g = *GImGui; const int old_size = g.DebugLogBuf.size(); - g.DebugLogBuf.appendf("[%05d] ", g.FrameCount); + if (g.ContextName[0] != 0) + g.DebugLogBuf.appendf("[%s] [%05d] ", g.ContextName, g.FrameCount); + else + g.DebugLogBuf.appendf("[%05d] ", g.FrameCount); g.DebugLogBuf.appendfv(fmt, args); g.DebugLogIndex.append(g.DebugLogBuf.c_str(), old_size, g.DebugLogBuf.size()); if (g.DebugLogFlags & ImGuiDebugLogFlags_OutputToTTY) @@ -15623,7 +22267,7 @@ static void SameLineOrWrap(const ImVec2& size) ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImVec2 pos(window->DC.CursorPosPrevLine.x + g.Style.ItemSpacing.x, window->DC.CursorPosPrevLine.y); - if (window->ClipRect.Contains(ImRect(pos, pos + size))) + if (window->WorkRect.Contains(ImRect(pos, pos + size))) ImGui::SameLine(); } @@ -15632,18 +22276,30 @@ static void ShowDebugLogFlag(const char* name, ImGuiDebugLogFlags flags) ImGuiContext& g = *GImGui; ImVec2 size(ImGui::GetFrameHeight() + g.Style.ItemInnerSpacing.x + ImGui::CalcTextSize(name).x, ImGui::GetFrameHeight()); SameLineOrWrap(size); // FIXME-LAYOUT: To be done automatically once we rework ItemSize/ItemAdd into ItemLayout. + + bool highlight_errors = (flags == ImGuiDebugLogFlags_EventError && g.DebugLogSkippedErrors > 0); + if (highlight_errors) + ImGui::PushStyleColor(ImGuiCol_Text, ImLerp(g.Style.Colors[ImGuiCol_Text], ImVec4(1.0f, 0.0f, 0.0f, 1.0f), 0.30f)); if (ImGui::CheckboxFlags(name, &g.DebugLogFlags, flags) && g.IO.KeyShift && (g.DebugLogFlags & flags) != 0) { g.DebugLogAutoDisableFrames = 2; g.DebugLogAutoDisableFlags |= flags; } - ImGui::SetItemTooltip("Hold SHIFT when clicking to enable for 2 frames only (useful for spammy log entries)"); + if (highlight_errors) + { + ImGui::PopStyleColor(); + ImGui::SetItemTooltip("%d past errors skipped.", g.DebugLogSkippedErrors); + } + else + { + ImGui::SetItemTooltip("Hold SHIFT when clicking to enable for 2 frames only (useful for spammy log entries)"); + } } void ImGui::ShowDebugLogWindow(bool* p_open) { ImGuiContext& g = *GImGui; - if (!(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize)) + if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0) SetNextWindowSize(ImVec2(0.0f, GetFontSize() * 12.0f), ImGuiCond_FirstUseEver); if (!Begin("Dear ImGui Debug Log", p_open) || GetCurrentWindow()->BeginCount > 1) { @@ -15655,24 +22311,45 @@ void ImGui::ShowDebugLogWindow(bool* p_open) CheckboxFlags("All", &g.DebugLogFlags, all_enable_flags); SetItemTooltip("(except InputRouting which is spammy)"); + ShowDebugLogFlag("Errors", ImGuiDebugLogFlags_EventError); ShowDebugLogFlag("ActiveId", ImGuiDebugLogFlags_EventActiveId); ShowDebugLogFlag("Clipper", ImGuiDebugLogFlags_EventClipper); + ShowDebugLogFlag("Docking", ImGuiDebugLogFlags_EventDocking); ShowDebugLogFlag("Focus", ImGuiDebugLogFlags_EventFocus); ShowDebugLogFlag("IO", ImGuiDebugLogFlags_EventIO); + //ShowDebugLogFlag("Font", ImGuiDebugLogFlags_EventFont); ShowDebugLogFlag("Nav", ImGuiDebugLogFlags_EventNav); ShowDebugLogFlag("Popup", ImGuiDebugLogFlags_EventPopup); - //ShowDebugLogFlag("Selection", ImGuiDebugLogFlags_EventSelection); + ShowDebugLogFlag("Selection", ImGuiDebugLogFlags_EventSelection); + ShowDebugLogFlag("Viewport", ImGuiDebugLogFlags_EventViewport); ShowDebugLogFlag("InputRouting", ImGuiDebugLogFlags_EventInputRouting); if (SmallButton("Clear")) { g.DebugLogBuf.clear(); g.DebugLogIndex.clear(); + g.DebugLogSkippedErrors = 0; } SameLine(); if (SmallButton("Copy")) SetClipboardText(g.DebugLogBuf.c_str()); - BeginChild("##log", ImVec2(0.0f, 0.0f), ImGuiChildFlags_Border, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar); + SameLine(); + if (SmallButton("Configure Outputs..")) + OpenPopup("Outputs"); + if (BeginPopup("Outputs")) + { + CheckboxFlags("OutputToTTY", &g.DebugLogFlags, ImGuiDebugLogFlags_OutputToTTY); +#ifndef IMGUI_ENABLE_TEST_ENGINE + BeginDisabled(); +#endif + CheckboxFlags("OutputToTestEngine", &g.DebugLogFlags, ImGuiDebugLogFlags_OutputToTestEngine); +#ifndef IMGUI_ENABLE_TEST_ENGINE + EndDisabled(); +#endif + EndPopup(); + } + + BeginChild("##log", ImVec2(0.0f, 0.0f), ImGuiChildFlags_Borders, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar); const ImGuiDebugLogFlags backup_log_flags = g.DebugLogFlags; g.DebugLogFlags &= ~ImGuiDebugLogFlags_EventClipper; @@ -15701,7 +22378,7 @@ void ImGui::DebugTextUnformattedWithLocateItem(const char* line_begin, const cha for (const char* p = line_begin; p <= line_end - 10; p++) { ImGuiID id = 0; - if (p[0] != '0' || (p[1] != 'x' && p[1] != 'X') || sscanf(p + 2, "%X", &id) != 1) + if (p[0] != '0' || (p[1] != 'x' && p[1] != 'X') || sscanf(p + 2, "%X", &id) != 1 || ImCharIsXdigitA(p[10])) continue; ImVec2 p0 = CalcTextSize(line_begin, p); ImVec2 p1 = CalcTextSize(p, p + 10); @@ -15944,7 +22621,7 @@ static int StackToolFormatLevelInfo(ImGuiIDStackTool* tool, int n, bool format_f void ImGui::ShowIDStackToolWindow(bool* p_open) { ImGuiContext& g = *GImGui; - if (!(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize)) + if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0) SetNextWindowSize(ImVec2(0.0f, GetFontSize() * 8.0f), ImGuiCond_FirstUseEver); if (!Begin("Dear ImGui ID Stack Tool", p_open) || GetCurrentWindow()->BeginCount > 1) { @@ -16032,8 +22709,6 @@ void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings*) {} void ImGui::DebugNodeWindowsList(ImVector*, const char*) {} void ImGui::DebugNodeViewport(ImGuiViewportP*) {} -void ImGui::DebugLog(const char*, ...) {} -void ImGui::DebugLogV(const char*, va_list) {} void ImGui::ShowDebugLogWindow(bool*) {} void ImGui::ShowIDStackToolWindow(bool*) {} void ImGui::DebugStartItemPicker() {} diff --git a/ext/imgui/imgui.h b/ext/imgui/imgui.h index 6b41c09734a3..2145c0e49fe0 100644 --- a/ext/imgui/imgui.h +++ b/ext/imgui/imgui.h @@ -1,16 +1,20 @@ -// dear imgui, v1.90.9 WIP +// dear imgui, v1.91.6 // (headers) +// PPSSPP hack +#undef new + // Help: // - See links below. // - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that. // - Read top of imgui.cpp for more details, links and comments. +// - Add '#define IMGUI_DEFINE_MATH_OPERATORS' before including this file (or in imconfig.h) to access courtesy maths operators for ImVec2 and ImVec4. // Resources: // - FAQ ........................ https://dearimgui.com/faq (in repository as docs/FAQ.md) // - Homepage ................... https://github.com/ocornut/imgui // - Releases & changelog ....... https://github.com/ocornut/imgui/releases -// - Gallery .................... https://github.com/ocornut/imgui/issues/7503 (please post your screenshots/video there!) +// - Gallery .................... https://github.com/ocornut/imgui/issues?q=label%3Agallery (please post your screenshots/video there!) // - Wiki ....................... https://github.com/ocornut/imgui/wiki (lots of good stuff there) // - Getting Started https://github.com/ocornut/imgui/wiki/Getting-Started (how to integrate in an existing app by adding ~25 lines of code) // - Third-party Extensions https://github.com/ocornut/imgui/wiki/Useful-Extensions (ImPlot & many more) @@ -27,9 +31,11 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') -#define IMGUI_VERSION "1.90.9 WIP" -#define IMGUI_VERSION_NUM 19081 +#define IMGUI_VERSION "1.91.6" +#define IMGUI_VERSION_NUM 19160 #define IMGUI_HAS_TABLE +#define IMGUI_HAS_VIEWPORT // Viewport WIP branch +#define IMGUI_HAS_DOCK // Docking WIP branch /* @@ -39,15 +45,16 @@ Index of this file: // [SECTION] Dear ImGui end-user API functions // [SECTION] Flags & Enumerations // [SECTION] Tables API flags and structures (ImGuiTableFlags, ImGuiTableColumnFlags, ImGuiTableRowFlags, ImGuiTableBgTarget, ImGuiTableSortSpecs, ImGuiTableColumnSortSpecs) -// [SECTION] Helpers: Memory allocations macros, ImVector<> +// [SECTION] Helpers: Debug log, Memory allocations macros, ImVector<> // [SECTION] ImGuiStyle // [SECTION] ImGuiIO -// [SECTION] Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiPayload) +// [SECTION] Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiWindowClass, ImGuiPayload) // [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor) +// [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectIO, ImGuiSelectionRequest, ImGuiSelectionBasicStorage, ImGuiSelectionExternalStorage) // [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData) // [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont) // [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport) -// [SECTION] Platform Dependent Interfaces (ImGuiPlatformImeData) +// [SECTION] ImGuiPlatformIO + other Platform Dependent Interfaces (ImGuiPlatformMonitor, ImGuiPlatformImeData) // [SECTION] Obsolete functions and types */ @@ -128,15 +135,16 @@ Index of this file: #pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' #endif #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' -#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast #pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe -#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant #pragma clang diagnostic ignored "-Wreserved-identifier" // warning: identifier '_Xxx' is reserved because it starts with '_' followed by a capital letter #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type #elif defined(__GNUC__) #pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind -#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #endif //----------------------------------------------------------------------------- @@ -170,13 +178,19 @@ struct ImFontGlyph; // A single font glyph (code point + coordin struct ImFontGlyphRangesBuilder; // Helper to build glyph ranges from text/string data struct ImColor; // Helper functions to create a color that can be converted to either u32 or float4 (*OBSOLETE* please avoid using) struct ImGuiContext; // Dear ImGui context (opaque structure, unless including imgui_internal.h) -struct ImGuiIO; // Main configuration and I/O between your application and ImGui +struct ImGuiIO; // Main configuration and I/O between your application and ImGui (also see: ImGuiPlatformIO) struct ImGuiInputTextCallbackData; // Shared state of InputText() when using custom ImGuiInputTextCallback (rare/advanced use) struct ImGuiKeyData; // Storage for ImGuiIO and IsKeyDown(), IsKeyPressed() etc functions. struct ImGuiListClipper; // Helper to manually clip large list of items +struct ImGuiMultiSelectIO; // Structure to interact with a BeginMultiSelect()/EndMultiSelect() block struct ImGuiOnceUponAFrame; // Helper for running a block of code not more than once a frame struct ImGuiPayload; // User data payload for drag and drop operations -struct ImGuiPlatformImeData; // Platform IME data for io.SetPlatformImeDataFn() function. +struct ImGuiPlatformIO; // Interface between platform/renderer backends and ImGui (e.g. Clipboard, IME, Multi-Viewport support). Extends ImGuiIO. +struct ImGuiPlatformImeData; // Platform IME data for io.PlatformSetImeDataFn() function. +struct ImGuiPlatformMonitor; // Multi-viewport support: user-provided bounds for each connected monitor/display. Used when positioning popups and tooltips to avoid them straddling monitors +struct ImGuiSelectionBasicStorage; // Optional helper to store multi-selection state + apply multi-selection requests. +struct ImGuiSelectionExternalStorage;//Optional helper to apply multi-selection requests to existing randomly accessible storage. +struct ImGuiSelectionRequest; // A selection request (stored in ImGuiMultiSelectIO) struct ImGuiSizeCallbackData; // Callback data when using SetNextWindowSizeConstraints() (rare/advanced use) struct ImGuiStorage; // Helper for key->value storage (container sorted by key) struct ImGuiStoragePair; // Helper for key->value storage (pair) @@ -185,7 +199,8 @@ struct ImGuiTableSortSpecs; // Sorting specifications for a table (often struct ImGuiTableColumnSortSpecs; // Sorting specification for one column of a table struct ImGuiTextBuffer; // Helper to hold and append into a text buffer (~string builder) struct ImGuiTextFilter; // Helper to parse and apply text filters (e.g. "aaaaa[,bbbbb][,ccccc]") -struct ImGuiViewport; // A Platform Window (always only one in 'master' branch), in the future may represent Platform Monitor +struct ImGuiViewport; // A Platform Window (always 1 unless multi-viewport are enabled. One per platform window to output to). In the future may represent Platform Monitor +struct ImGuiWindowClass; // Window class (rare/advanced uses: provide hints to the platform backend via altered viewport flags and parent/child info) // Enumerations // - We don't use strongly typed enums much because they add constraints (can't extend in private code, can't store typed in bit fields, extra casting on iteration) @@ -219,13 +234,16 @@ typedef int ImGuiChildFlags; // -> enum ImGuiChildFlags_ // Flags: f typedef int ImGuiColorEditFlags; // -> enum ImGuiColorEditFlags_ // Flags: for ColorEdit4(), ColorPicker4() etc. typedef int ImGuiConfigFlags; // -> enum ImGuiConfigFlags_ // Flags: for io.ConfigFlags typedef int ImGuiComboFlags; // -> enum ImGuiComboFlags_ // Flags: for BeginCombo() +typedef int ImGuiDockNodeFlags; // -> enum ImGuiDockNodeFlags_ // Flags: for DockSpace() typedef int ImGuiDragDropFlags; // -> enum ImGuiDragDropFlags_ // Flags: for BeginDragDropSource(), AcceptDragDropPayload() typedef int ImGuiFocusedFlags; // -> enum ImGuiFocusedFlags_ // Flags: for IsWindowFocused() typedef int ImGuiHoveredFlags; // -> enum ImGuiHoveredFlags_ // Flags: for IsItemHovered(), IsWindowHovered() etc. typedef int ImGuiInputFlags; // -> enum ImGuiInputFlags_ // Flags: for Shortcut(), SetNextItemShortcut() typedef int ImGuiInputTextFlags; // -> enum ImGuiInputTextFlags_ // Flags: for InputText(), InputTextMultiline() +typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag(), shared by all items typedef int ImGuiKeyChord; // -> ImGuiKey | ImGuiMod_XXX // Flags: for IsKeyChordPressed(), Shortcut() etc. an ImGuiKey optionally OR-ed with one or more ImGuiMod_XXX values. typedef int ImGuiPopupFlags; // -> enum ImGuiPopupFlags_ // Flags: for OpenPopup*(), BeginPopupContext*(), IsPopupOpen() +typedef int ImGuiMultiSelectFlags; // -> enum ImGuiMultiSelectFlags_// Flags: for BeginMultiSelect() typedef int ImGuiSelectableFlags; // -> enum ImGuiSelectableFlags_ // Flags: for Selectable() typedef int ImGuiSliderFlags; // -> enum ImGuiSliderFlags_ // Flags: for DragFloat(), DragInt(), SliderFloat(), SliderInt() etc. typedef int ImGuiTabBarFlags; // -> enum ImGuiTabBarFlags_ // Flags: for BeginTabBar() @@ -240,8 +258,10 @@ typedef int ImGuiWindowFlags; // -> enum ImGuiWindowFlags_ // Flags: f // ImTexture: user data for renderer backend to identify a texture [Compile-time configurable type] // - To use something else than an opaque void* pointer: override with e.g. '#define ImTextureID MyTextureType*' in your imconfig.h file. // - This can be whatever to you want it to be! read the FAQ about ImTextureID for details. +// - You can make this a structure with various constructors if you need. You will have to implement ==/!= operators. +// - (note: before v1.91.4 (2024/10/08) the default type for ImTextureID was void*. Use intermediary intptr_t cast and read FAQ if you have casting warnings) #ifndef ImTextureID -typedef void* ImTextureID; // Default: store a pointer or an integer fitting in a pointer (most renderer backends are ok with that) +typedef ImU64 ImTextureID; // Default: store a pointer or an integer fitting in a pointer (most renderer backends are ok with that) #endif // ImDrawIdx: vertex index. [Compile-time configurable type] @@ -261,6 +281,11 @@ typedef ImWchar32 ImWchar; typedef ImWchar16 ImWchar; #endif +// Multi-Selection item index or identifier when using BeginMultiSelect() +// - Used by SetNextItemSelectionUserData() + and inside ImGuiMultiSelectIO structure. +// - Most users are likely to use this store an item INDEX but this may be used to store a POINTER/ID as well. Read comments near ImGuiMultiSelectIO for details. +typedef ImS64 ImGuiSelectionUserData; + // Callback and functions types typedef int (*ImGuiInputTextCallback)(ImGuiInputTextCallbackData* data); // Callback function for ImGui::InputText() typedef void (*ImGuiSizeCallback)(ImGuiSizeCallbackData* data); // Callback function for ImGui::SetNextWindowSizeConstraints() @@ -268,8 +293,8 @@ typedef void* (*ImGuiMemAllocFunc)(size_t sz, void* user_data); typedef void (*ImGuiMemFreeFunc)(void* ptr, void* user_data); // Function signature for ImGui::SetAllocatorFunctions() // ImVec2: 2D vector used to store positions, sizes etc. [Compile-time configurable type] -// This is a frequently used type in the API. Consider using IM_VEC2_CLASS_EXTRA to create implicit cast from/to our preferred type. -// Add '#define IMGUI_DEFINE_MATH_OPERATORS' in your imconfig.h file to benefit from courtesy maths operators for those types. +// - This is a frequently used type in the API. Consider using IM_VEC2_CLASS_EXTRA to create implicit cast from/to our preferred type. +// - Add '#define IMGUI_DEFINE_MATH_OPERATORS' before including this file (or in imconfig.h) to access courtesy maths operators for ImVec2 and ImVec4. IM_MSVC_RUNTIME_CHECKS_OFF struct ImVec2 { @@ -312,7 +337,8 @@ namespace ImGui IMGUI_API void SetCurrentContext(ImGuiContext* ctx); // Main - IMGUI_API ImGuiIO& GetIO(); // access the IO structure (mouse/keyboard/gamepad inputs, time, various configuration options/flags) + IMGUI_API ImGuiIO& GetIO(); // access the ImGuiIO structure (mouse/keyboard/gamepad inputs, time, various configuration options/flags) + IMGUI_API ImGuiPlatformIO& GetPlatformIO(); // access the ImGuiPlatformIO structure (mostly hooks/functions to connect to platform/renderer and OS Clipboard, IME etc.) IMGUI_API ImGuiStyle& GetStyle(); // access the Style structure (colors, sizes). Always use PushStyleColor(), PushStyleVar() to modify style mid-frame! IMGUI_API void NewFrame(); // start a new Dear ImGui frame, you can submit any command from this point until Render()/EndFrame(). IMGUI_API void EndFrame(); // ends the Dear ImGui frame. automatically called by Render(). If you don't need to render data (skipping rendering) you may call EndFrame() without Render()... but you'll have wasted CPU already! If you don't need to render, better to not create any windows and not call NewFrame() at all! @@ -354,10 +380,10 @@ namespace ImGui // Child Windows // - Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window. Child windows can embed their own child. // - Before 1.90 (November 2023), the "ImGuiChildFlags child_flags = 0" parameter was "bool border = false". - // This API is backward compatible with old code, as we guarantee that ImGuiChildFlags_Border == true. + // This API is backward compatible with old code, as we guarantee that ImGuiChildFlags_Borders == true. // Consider updating your old code: // BeginChild("Name", size, false) -> Begin("Name", size, 0); or Begin("Name", size, ImGuiChildFlags_None); - // BeginChild("Name", size, true) -> Begin("Name", size, ImGuiChildFlags_Border); + // BeginChild("Name", size, true) -> Begin("Name", size, ImGuiChildFlags_Borders); // - Manual sizing (each axis can use a different setting e.g. ImVec2(0.0f, 400.0f)): // == 0.0f: use remaining parent window size for this axis. // > 0.0f: use specified size for this axis. @@ -380,10 +406,12 @@ namespace ImGui IMGUI_API bool IsWindowFocused(ImGuiFocusedFlags flags=0); // is current window focused? or its root/child, depending on flags. see flags for options. IMGUI_API bool IsWindowHovered(ImGuiHoveredFlags flags=0); // is current window hovered and hoverable (e.g. not blocked by a popup/modal)? See ImGuiHoveredFlags_ for options. IMPORTANT: If you are trying to check whether your mouse should be dispatched to Dear ImGui or to your underlying app, you should not use this function! Use the 'io.WantCaptureMouse' boolean for that! Refer to FAQ entry "How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?" for details. IMGUI_API ImDrawList* GetWindowDrawList(); // get draw list associated to the current window, to append your own drawing primitives - IMGUI_API ImVec2 GetWindowPos(); // get current window position in screen space (note: it is unlikely you need to use this. Consider using current layout pos instead, GetCursorScreenPos()) - IMGUI_API ImVec2 GetWindowSize(); // get current window size (note: it is unlikely you need to use this. Consider using GetCursorScreenPos() and e.g. GetContentRegionAvail() instead) - IMGUI_API float GetWindowWidth(); // get current window width (shortcut for GetWindowSize().x) - IMGUI_API float GetWindowHeight(); // get current window height (shortcut for GetWindowSize().y) + IMGUI_API float GetWindowDpiScale(); // get DPI scale currently associated to the current window's viewport. + IMGUI_API ImVec2 GetWindowPos(); // get current window position in screen space (IT IS UNLIKELY YOU EVER NEED TO USE THIS. Consider always using GetCursorScreenPos() and GetContentRegionAvail() instead) + IMGUI_API ImVec2 GetWindowSize(); // get current window size (IT IS UNLIKELY YOU EVER NEED TO USE THIS. Consider always using GetCursorScreenPos() and GetContentRegionAvail() instead) + IMGUI_API float GetWindowWidth(); // get current window width (IT IS UNLIKELY YOU EVER NEED TO USE THIS). Shortcut for GetWindowSize().x. + IMGUI_API float GetWindowHeight(); // get current window height (IT IS UNLIKELY YOU EVER NEED TO USE THIS). Shortcut for GetWindowSize().y. + IMGUI_API ImGuiViewport*GetWindowViewport(); // get viewport currently associated to the current window. // Window manipulation // - Prefer using SetNextXXX functions (before Begin) rather that SetXXX functions (after Begin). @@ -395,6 +423,7 @@ namespace ImGui IMGUI_API void SetNextWindowFocus(); // set next window to be focused / top-most. call before Begin() IMGUI_API void SetNextWindowScroll(const ImVec2& scroll); // set next window scrolling value (use < 0.0f to not affect a given axis). IMGUI_API void SetNextWindowBgAlpha(float alpha); // set next window background color alpha. helper to easily override the Alpha component of ImGuiCol_WindowBg/ChildBg/PopupBg. you may also use ImGuiWindowFlags_NoBackground. + IMGUI_API void SetNextWindowViewport(ImGuiID viewport_id); // set next window viewport IMGUI_API void SetWindowPos(const ImVec2& pos, ImGuiCond cond = 0); // (not recommended) set current window position - call within Begin()/End(). prefer using SetNextWindowPos(), as this may incur tearing and side-effects. IMGUI_API void SetWindowSize(const ImVec2& size, ImGuiCond cond = 0); // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0, 0) to force an auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects. IMGUI_API void SetWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // (not recommended) set current window collapsed state. prefer using SetNextWindowCollapsed(). @@ -405,14 +434,6 @@ namespace ImGui IMGUI_API void SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond = 0); // set named window collapsed state IMGUI_API void SetWindowFocus(const char* name); // set named window to be focused / top-most. use NULL to remove focus. - // Content region - // - Retrieve available space from a given point. GetContentRegionAvail() is frequently useful. - // - Those functions are bound to be redesigned (they are confusing, incomplete and the Min/Max return values are in local window coordinates which increases confusion) - IMGUI_API ImVec2 GetContentRegionAvail(); // == GetContentRegionMax() - GetCursorPos() - IMGUI_API ImVec2 GetContentRegionMax(); // current content boundaries (typically window boundaries including scrolling, or current column boundaries), in windows coordinates - IMGUI_API ImVec2 GetWindowContentRegionMin(); // content boundaries min for the full window (roughly (0,0)-Scroll), in window coordinates - IMGUI_API ImVec2 GetWindowContentRegionMax(); // content boundaries max for the full window (roughly (0,0)+Size-Scroll) where Size can be overridden with SetNextWindowContentSize(), in window coordinates - // Windows Scrolling // - Any change of Scroll will be applied at the beginning of next frame in the first call to Begin(). // - You may instead use SetNextWindowScroll() prior to calling Begin() to avoid this delay, as an alternative to using SetScrollX()/SetScrollY(). @@ -433,13 +454,13 @@ namespace ImGui IMGUI_API void PushStyleColor(ImGuiCol idx, ImU32 col); // modify a style color. always use this if you modify the style after NewFrame(). IMGUI_API void PushStyleColor(ImGuiCol idx, const ImVec4& col); IMGUI_API void PopStyleColor(int count = 1); - IMGUI_API void PushStyleVar(ImGuiStyleVar idx, float val); // modify a style float variable. always use this if you modify the style after NewFrame(). - IMGUI_API void PushStyleVar(ImGuiStyleVar idx, const ImVec2& val); // modify a style ImVec2 variable. always use this if you modify the style after NewFrame(). + IMGUI_API void PushStyleVar(ImGuiStyleVar idx, float val); // modify a style float variable. always use this if you modify the style after NewFrame()! + IMGUI_API void PushStyleVar(ImGuiStyleVar idx, const ImVec2& val); // modify a style ImVec2 variable. " + IMGUI_API void PushStyleVarX(ImGuiStyleVar idx, float val_x); // modify X component of a style ImVec2 variable. " + IMGUI_API void PushStyleVarY(ImGuiStyleVar idx, float val_y); // modify Y component of a style ImVec2 variable. " IMGUI_API void PopStyleVar(int count = 1); - IMGUI_API void PushTabStop(bool tab_stop); // == tab stop enable. Allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets - IMGUI_API void PopTabStop(); - IMGUI_API void PushButtonRepeat(bool repeat); // in 'repeat' mode, Button*() functions return repeated true in a typematic manner (using io.KeyRepeatDelay/io.KeyRepeatRate setting). Note that you can call IsItemActive() after any Button() to tell if the button is held in the current frame. - IMGUI_API void PopButtonRepeat(); + IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); // modify specified shared item flag, e.g. PushItemFlag(ImGuiItemFlags_NoTabStop, true) + IMGUI_API void PopItemFlag(); // Parameters stacks (current window) IMGUI_API void PushItemWidth(float item_width); // push width of items for common large "item+label" widgets. >0.0f: width in pixels, <0.0f align xx pixels to the right of window (so -FLT_MIN always align width to the right side). @@ -453,7 +474,7 @@ namespace ImGui // - Use the ShowStyleEditor() function to interactively see/edit the colors. IMGUI_API ImFont* GetFont(); // get current font IMGUI_API float GetFontSize(); // get current font size (= height in pixels) of current font with current scale applied - IMGUI_API ImVec2 GetFontTexUvWhitePixel(); // get UV coordinate for a while pixel, useful to draw custom shapes via the ImDrawList API + IMGUI_API ImVec2 GetFontTexUvWhitePixel(); // get UV coordinate for a white pixel, useful to draw custom shapes via the ImDrawList API IMGUI_API ImU32 GetColorU32(ImGuiCol idx, float alpha_mul = 1.0f); // retrieve given style color with style alpha applied and optional extra alpha multiplier, packed as a 32-bit value suitable for ImDrawList IMGUI_API ImU32 GetColorU32(const ImVec4& col); // retrieve given color with style alpha applied, packed as a 32-bit value suitable for ImDrawList IMGUI_API ImU32 GetColorU32(ImU32 col, float alpha_mul = 1.0f); // retrieve given color with style alpha applied, packed as a 32-bit value suitable for ImDrawList @@ -463,19 +484,22 @@ namespace ImGui // - By "cursor" we mean the current output position. // - The typical widget behavior is to output themselves at the current cursor position, then move the cursor one line down. // - You can call SameLine() between widgets to undo the last carriage return and output at the right of the preceding widget. + // - YOU CAN DO 99% OF WHAT YOU NEED WITH ONLY GetCursorScreenPos() and GetContentRegionAvail(). // - Attention! We currently have inconsistencies between window-local and absolute positions we will aim to fix with future API: // - Absolute coordinate: GetCursorScreenPos(), SetCursorScreenPos(), all ImDrawList:: functions. -> this is the preferred way forward. - // - Window-local coordinates: SameLine(), GetCursorPos(), SetCursorPos(), GetCursorStartPos(), GetContentRegionMax(), GetWindowContentRegion*(), PushTextWrapPos() - // - GetCursorScreenPos() = GetCursorPos() + GetWindowPos(). GetWindowPos() is almost only ever useful to convert from window-local to absolute coordinates. - IMGUI_API ImVec2 GetCursorScreenPos(); // cursor position in absolute coordinates (prefer using this, also more useful to work with ImDrawList API). - IMGUI_API void SetCursorScreenPos(const ImVec2& pos); // cursor position in absolute coordinates - IMGUI_API ImVec2 GetCursorPos(); // [window-local] cursor position in window coordinates (relative to window position) + // - Window-local coordinates: SameLine(offset), GetCursorPos(), SetCursorPos(), GetCursorStartPos(), PushTextWrapPos() + // - Window-local coordinates: GetContentRegionMax(), GetWindowContentRegionMin(), GetWindowContentRegionMax() --> all obsoleted. YOU DON'T NEED THEM. + // - GetCursorScreenPos() = GetCursorPos() + GetWindowPos(). GetWindowPos() is almost only ever useful to convert from window-local to absolute coordinates. Try not to use it. + IMGUI_API ImVec2 GetCursorScreenPos(); // cursor position, absolute coordinates. THIS IS YOUR BEST FRIEND (prefer using this rather than GetCursorPos(), also more useful to work with ImDrawList API). + IMGUI_API void SetCursorScreenPos(const ImVec2& pos); // cursor position, absolute coordinates. THIS IS YOUR BEST FRIEND. + IMGUI_API ImVec2 GetContentRegionAvail(); // available space from current position. THIS IS YOUR BEST FRIEND. + IMGUI_API ImVec2 GetCursorPos(); // [window-local] cursor position in window-local coordinates. This is not your best friend. IMGUI_API float GetCursorPosX(); // [window-local] " IMGUI_API float GetCursorPosY(); // [window-local] " IMGUI_API void SetCursorPos(const ImVec2& local_pos); // [window-local] " IMGUI_API void SetCursorPosX(float local_x); // [window-local] " IMGUI_API void SetCursorPosY(float local_y); // [window-local] " - IMGUI_API ImVec2 GetCursorStartPos(); // [window-local] initial cursor position, in window coordinates + IMGUI_API ImVec2 GetCursorStartPos(); // [window-local] initial cursor position, in window-local coordinates. Call GetCursorScreenPos() after Begin() to get the absolute coordinates version. // Other layout functions IMGUI_API void Separator(); // separator, generally horizontal. inside a menu bar or in horizontal layout mode, this becomes a vertical separator. @@ -512,6 +536,7 @@ namespace ImGui IMGUI_API ImGuiID GetID(const char* str_id); // calculate unique ID (hash of whole ID stack + given parameter). e.g. if you want to query into ImGuiStorage yourself IMGUI_API ImGuiID GetID(const char* str_id_begin, const char* str_id_end); IMGUI_API ImGuiID GetID(const void* ptr_id); + IMGUI_API ImGuiID GetID(int int_id); // Widgets: Text IMGUI_API void TextUnformatted(const char* text, const char* text_end = NULL); // raw text without formatting. Roughly equivalent to Text("%s", text) but: A) doesn't require null terminated string if 'text_end' is specified, B) it's faster, no memory copy is done, no buffer size limits, recommended for long chunks of text. @@ -543,11 +568,14 @@ namespace ImGui IMGUI_API bool RadioButton(const char* label, int* v, int v_button); // shortcut to handle the above pattern when value is an integer IMGUI_API void ProgressBar(float fraction, const ImVec2& size_arg = ImVec2(-FLT_MIN, 0), const char* overlay = NULL); IMGUI_API void Bullet(); // draw a small circle + keep the cursor on the same line. advance cursor x position by GetTreeNodeToLabelSpacing(), same distance that TreeNode() uses + IMGUI_API bool TextLink(const char* label); // hyperlink text button, return true when clicked + IMGUI_API void TextLinkOpenURL(const char* label, const char* url = NULL); // hyperlink text button, automatically open file/url when clicked // Widgets: Images // - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples // - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above. // - Note that Image() may add +2.0f to provided size if a border is visible, ImageButton() adds style.FramePadding*2.0f to provided size. + // - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& tint_col = ImVec4(1, 1, 1, 1), const ImVec4& border_col = ImVec4(0, 0, 0, 0)); IMGUI_API bool ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); @@ -566,7 +594,7 @@ namespace ImGui // the array syntax is just a way to document the number of elements that are expected to be accessible. You can pass address of your first element out of a contiguous set, e.g. &myvector.x // - Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc. // - Format string may also be set to NULL or use the default format ("%f" or "%d"). - // - Speed are per-pixel of mouse movement (v_speed=0.2f: mouse needs to move by 5 pixels to increase value by 1). For gamepad/keyboard navigation, minimum speed is Max(v_speed, minimum_step_at_given_precision). + // - Speed are per-pixel of mouse movement (v_speed=0.2f: mouse needs to move by 5 pixels to increase value by 1). For keyboard/gamepad navigation, minimum speed is Max(v_speed, minimum_step_at_given_precision). // - Use v_min < v_max to clamp edits to given limits. Note that CTRL+Click manual input can override those limits if ImGuiSliderFlags_AlwaysClamp is not used. // - Use v_max = FLT_MAX / INT_MAX etc to avoid clamping to a maximum, same with v_min = -FLT_MAX / INT_MIN to avoid clamping to a minimum. // - We use the same sets of flags for DragXXX() and SliderXXX() functions as the features are the same and it makes it easier to swap them. @@ -653,6 +681,7 @@ namespace ImGui IMGUI_API bool CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags = 0); // if returning 'true' the header is open. doesn't indent nor push on ID stack. user doesn't have to call TreePop(). IMGUI_API bool CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFlags flags = 0); // when 'p_visible != NULL': if '*p_visible==true' display an additional small close button on upper right of the header which will set the bool to false when clicked, if '*p_visible==false' don't display the header. IMGUI_API void SetNextItemOpen(bool is_open, ImGuiCond cond = 0); // set next TreeNode/CollapsingHeader open state. + IMGUI_API void SetNextItemStorageID(ImGuiID storage_id); // set id to use for open/close storage (default to same as item id). // Widgets: Selectables // - A selectable highlights when hovered, and can display another color when selected. @@ -660,6 +689,18 @@ namespace ImGui IMGUI_API bool Selectable(const char* label, bool selected = false, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0, 0)); // "bool selected" carry the selection state (read-only). Selectable() is clicked is returns true so you can modify your selection state. size.x==0.0: use remaining width, size.x>0.0: specify width. size.y==0.0: use label height, size.y>0.0: specify height IMGUI_API bool Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0, 0)); // "bool* p_selected" point to the selection state (read-write), as a convenient helper. + // Multi-selection system for Selectable(), Checkbox(), TreeNode() functions [BETA] + // - This enables standard multi-selection/range-selection idioms (CTRL+Mouse/Keyboard, SHIFT+Mouse/Keyboard, etc.) in a way that also allow a clipper to be used. + // - ImGuiSelectionUserData is often used to store your item index within the current view (but may store something else). + // - Read comments near ImGuiMultiSelectIO for instructions/details and see 'Demo->Widgets->Selection State & Multi-Select' for demo. + // - TreeNode() is technically supported but... using this correctly is more complicated. You need some sort of linear/random access to your tree, + // which is suited to advanced trees setups already implementing filters and clipper. We will work simplifying the current demo. + // - 'selection_size' and 'items_count' parameters are optional and used by a few features. If they are costly for you to compute, you may avoid them. + IMGUI_API ImGuiMultiSelectIO* BeginMultiSelect(ImGuiMultiSelectFlags flags, int selection_size = -1, int items_count = -1); + IMGUI_API ImGuiMultiSelectIO* EndMultiSelect(); + IMGUI_API void SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data); + IMGUI_API bool IsItemToggledSelection(); // Was the last item selection state toggled? Useful if you need the per-item information _before_ reaching EndMultiSelect(). We only returns toggle _event_ in order to handle clipping correctly. + // Widgets: List Boxes // - This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label. // - You can submit contents and manage your selection state however you want it, by creating e.g. Selectable() or any other items. @@ -779,7 +820,7 @@ namespace ImGui // - TableNextColumn() -> Text("Hello 0") -> TableNextColumn() -> Text("Hello 1") // OK: TableNextColumn() automatically gets to next row! // - TableNextRow() -> Text("Hello 0") // Not OK! Missing TableSetColumnIndex() or TableNextColumn()! Text will not appear! // - 5. Call EndTable() - IMGUI_API bool BeginTable(const char* str_id, int column, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0.0f, 0.0f), float inner_width = 0.0f); + IMGUI_API bool BeginTable(const char* str_id, int columns, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0.0f, 0.0f), float inner_width = 0.0f); IMGUI_API void EndTable(); // only call EndTable() if BeginTable() returns true! IMGUI_API void TableNextRow(ImGuiTableRowFlags row_flags = 0, float min_row_height = 0.0f); // append into the first cell of a new row. IMGUI_API bool TableNextColumn(); // append into the next column (or first column of next row if currently in last column). Return true when column is visible. @@ -812,11 +853,12 @@ namespace ImGui IMGUI_API const char* TableGetColumnName(int column_n = -1); // return "" if column didn't have a name declared by TableSetupColumn(). Pass -1 to use current column. IMGUI_API ImGuiTableColumnFlags TableGetColumnFlags(int column_n = -1); // return column flags so you can query their Enabled/Visible/Sorted/Hovered status flags. Pass -1 to use current column. IMGUI_API void TableSetColumnEnabled(int column_n, bool v);// change user accessible enabled/disabled state of a column. Set to false to hide the column. User can use the context menu to change this themselves (right-click in headers, or right-click in columns body with ImGuiTableFlags_ContextMenuInBody) + IMGUI_API int TableGetHoveredColumn(); // return hovered column. return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. Can also use (TableGetColumnFlags() & ImGuiTableColumnFlags_IsHovered) instead. IMGUI_API void TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n = -1); // change the color of a cell, row, or column. See ImGuiTableBgTarget_ flags for details. // Legacy Columns API (prefer using Tables!) // - You can also use SameLine(pos_x) to mimic simplified columns. - IMGUI_API void Columns(int count = 1, const char* id = NULL, bool border = true); + IMGUI_API void Columns(int count = 1, const char* id = NULL, bool borders = true); IMGUI_API void NextColumn(); // next column, defaults to current row or next row if the current row is finished IMGUI_API int GetColumnIndex(); // get current column index IMGUI_API float GetColumnWidth(int column_index = -1); // get column width (in pixels). pass -1 to use current column @@ -834,6 +876,26 @@ namespace ImGui IMGUI_API bool TabItemButton(const char* label, ImGuiTabItemFlags flags = 0); // create a Tab behaving like a button. return true when clicked. cannot be selected in the tab bar. IMGUI_API void SetTabItemClosed(const char* tab_or_docked_window_label); // notify TabBar or Docking system of a closed tab/window ahead (useful to reduce visual flicker on reorderable tab bars). For tab-bar: call after BeginTabBar() and before Tab submissions. Otherwise call with a window name. + // Docking + // [BETA API] Enable with io.ConfigFlags |= ImGuiConfigFlags_DockingEnable. + // Note: You can use most Docking facilities without calling any API. You DO NOT need to call DockSpace() to use Docking! + // - Drag from window title bar or their tab to dock/undock. Hold SHIFT to disable docking. + // - Drag from window menu button (upper-left button) to undock an entire node (all windows). + // - When io.ConfigDockingWithShift == true, you instead need to hold SHIFT to enable docking. + // About dockspaces: + // - Use DockSpaceOverViewport() to create a window covering the screen or a specific viewport + a dockspace inside it. + // This is often used with ImGuiDockNodeFlags_PassthruCentralNode to make it transparent. + // - Use DockSpace() to create an explicit dock node _within_ an existing window. See Docking demo for details. + // - Important: Dockspaces need to be submitted _before_ any window they can host. Submit it early in your frame! + // - Important: Dockspaces need to be kept alive if hidden, otherwise windows docked into it will be undocked. + // e.g. if you have multiple tabs with a dockspace inside each tab: submit the non-visible dockspaces with ImGuiDockNodeFlags_KeepAliveOnly. + IMGUI_API ImGuiID DockSpace(ImGuiID dockspace_id, const ImVec2& size = ImVec2(0, 0), ImGuiDockNodeFlags flags = 0, const ImGuiWindowClass* window_class = NULL); + IMGUI_API ImGuiID DockSpaceOverViewport(ImGuiID dockspace_id = 0, const ImGuiViewport* viewport = NULL, ImGuiDockNodeFlags flags = 0, const ImGuiWindowClass* window_class = NULL); + IMGUI_API void SetNextWindowDockID(ImGuiID dock_id, ImGuiCond cond = 0); // set next window dock id + IMGUI_API void SetNextWindowClass(const ImGuiWindowClass* window_class); // set next window class (control docking compatibility + provide hints to platform backend via custom viewport flags and platform parent/child relationship) + IMGUI_API ImGuiID GetWindowDockID(); + IMGUI_API bool IsWindowDocked(); // is current window docked into another window? + // Logging/Capture // - All text output from the interface can be captured into tty/file/clipboard. By default, tree nodes are automatically opened during logging. IMGUI_API void LogToTTY(int auto_open_depth = -1); // start logging to tty (stdout) @@ -860,7 +922,8 @@ namespace ImGui // Disabling [BETA API] // - Disable all user interactions and dim items visuals (applying style.DisabledAlpha over current colors) // - Those can be nested but it cannot be used to enable an already disabled section (a single BeginDisabled(true) in the stack is enough to keep everything disabled) - // - BeginDisabled(false) essentially does nothing useful but is provided to facilitate use of boolean expressions. If you can avoid calling BeginDisabled(False)/EndDisabled() best to avoid it. + // - Tooltips windows by exception are opted out of disabling. + // - BeginDisabled(false)/EndDisabled() essentially does nothing but is provided to facilitate use of boolean expressions (as a micro-optimization: if you have tens of thousands of BeginDisabled(false)/EndDisabled() pairs, you might want to reformulate your code to avoid making those calls) IMGUI_API void BeginDisabled(bool disabled = true); IMGUI_API void EndDisabled(); @@ -870,10 +933,12 @@ namespace ImGui IMGUI_API void PopClipRect(); // Focus, Activation - // - Prefer using "SetItemDefaultFocus()" over "if (IsWindowAppearing()) SetScrollHereY()" when applicable to signify "this is the default item" - IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a window. + IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of of a newly appearing window. IMGUI_API void SetKeyboardFocusHere(int offset = 0); // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget. + // Keyboard/Gamepad Navigation + IMGUI_API void SetNavCursorVisible(bool visible); // alter visibility of keyboard/gamepad cursor. by default: show when using an arrow key, hide when clicking with mouse. + // Overlapping mode IMGUI_API void SetNextItemAllowOverlap(); // allow next item to be overlapped by a subsequent item. Useful with invisible buttons, selectable, treenode covering an area where subsequent items may need to be added. Note that both Selectable() and TreeNode() have dedicated flags doing this. @@ -905,8 +970,8 @@ namespace ImGui IMGUI_API ImGuiViewport* GetMainViewport(); // return primary/default viewport. This can never be NULL. // Background/Foreground Draw Lists - IMGUI_API ImDrawList* GetBackgroundDrawList(); // this draw list will be the first rendered one. Useful to quickly draw shapes/text behind dear imgui contents. - IMGUI_API ImDrawList* GetForegroundDrawList(); // this draw list will be the last rendered one. Useful to quickly draw shapes/text over dear imgui contents. + IMGUI_API ImDrawList* GetBackgroundDrawList(ImGuiViewport* viewport = NULL); // get background draw list for the given viewport or viewport associated to the current window. this draw list will be the first rendering one. Useful to quickly draw shapes/text behind dear imgui contents. + IMGUI_API ImDrawList* GetForegroundDrawList(ImGuiViewport* viewport = NULL); // get foreground draw list for the given viewport or viewport associated to the current window. this draw list will be the top-most rendered one. Useful to quickly draw shapes/text over dear imgui contents. // Miscellaneous Utilities IMGUI_API bool IsRectVisible(const ImVec2& size); // test if rectangle (of given size, starting from cursor position) is visible / not clipped. @@ -929,9 +994,8 @@ namespace ImGui // Inputs Utilities: Keyboard/Mouse/Gamepad // - the ImGuiKey enum contains all possible keyboard, mouse and gamepad inputs (e.g. ImGuiKey_A, ImGuiKey_MouseLeft, ImGuiKey_GamepadDpadUp...). - // - before v1.87, we used ImGuiKey to carry native/user indices as defined by each backends. About use of those legacy ImGuiKey values: - // - without IMGUI_DISABLE_OBSOLETE_KEYIO (legacy support): you can still use your legacy native/user indices (< 512) according to how your backend/engine stored them in io.KeysDown[], but need to cast them to ImGuiKey. - // - with IMGUI_DISABLE_OBSOLETE_KEYIO (this is the way forward): any use of ImGuiKey will assert with key < 512. GetKeyIndex() is pass-through and therefore deprecated (gone if IMGUI_DISABLE_OBSOLETE_KEYIO is defined). + // - (legacy: before v1.87, we used ImGuiKey to carry native/user indices as defined by each backends. This was obsoleted in 1.87 (2022-02) and completely removed in 1.91.5 (2024-11). See https://github.com/ocornut/imgui/issues/4921) + // - (legacy: any use of ImGuiKey will assert when key < 512 to detect passing legacy native/user indices) IMGUI_API bool IsKeyDown(ImGuiKey key); // is key being held. IMGUI_API bool IsKeyPressed(ImGuiKey key, bool repeat = true); // was key pressed (went from !Down to Down)? if repeat=true, uses io.KeyRepeatDelay / KeyRepeatRate IMGUI_API bool IsKeyReleased(ImGuiKey key); // was key released (went from Down to !Down)? @@ -958,7 +1022,15 @@ namespace ImGui IMGUI_API bool Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags = 0); IMGUI_API void SetNextItemShortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags = 0); - // Inputs Utilities: Mouse specific + // Inputs Utilities: Key/Input Ownership [BETA] + // - One common use case would be to allow your items to disable standard inputs behaviors such + // as Tab or Alt key handling, Mouse Wheel scrolling, etc. + // e.g. Button(...); SetItemKeyOwner(ImGuiKey_MouseWheelY); to make hovering/activating a button disable wheel for scrolling. + // - Reminder ImGuiKey enum include access to mouse buttons and gamepad, so key ownership can apply to them. + // - Many related features are still in imgui_internal.h. For instance, most IsKeyXXX()/IsMouseXXX() functions have an owner-id-aware version. + IMGUI_API void SetItemKeyOwner(ImGuiKey key); // Set key owner to last item ID if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'. + + // Inputs Utilities: Mouse // - To refer to a mouse button, you may use named enums in your code e.g. ImGuiMouseButton_Left, ImGuiMouseButton_Right. // - You can also use regular integer: it is forever guaranteed that 0=Left, 1=Right, 2=Middle. // - Dragging operations are only reported after mouse has moved a certain distance away from the initial clicking position (see 'lock_threshold' and 'io.MouseDraggingThreshold') @@ -999,6 +1071,10 @@ namespace ImGui IMGUI_API void DebugFlashStyleColor(ImGuiCol idx); IMGUI_API void DebugStartItemPicker(); IMGUI_API bool DebugCheckVersionAndDataLayout(const char* version_str, size_t sz_io, size_t sz_style, size_t sz_vec2, size_t sz_vec4, size_t sz_drawvert, size_t sz_drawidx); // This is called by IMGUI_CHECKVERSION() macro. +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + IMGUI_API void DebugLog(const char* fmt, ...) IM_FMTARGS(1); // Call via IMGUI_DEBUG_LOG() for maximum stripping in caller code! + IMGUI_API void DebugLogV(const char* fmt, va_list args) IM_FMTLIST(1); +#endif // Memory Allocators // - Those functions are not reliant on the current context. @@ -1009,6 +1085,15 @@ namespace ImGui IMGUI_API void* MemAlloc(size_t size); IMGUI_API void MemFree(void* ptr); + // (Optional) Platform/OS interface for multi-viewport support + // Read comments around the ImGuiPlatformIO structure for more details. + // Note: You may use GetWindowViewport() to get the current viewport of the current window. + IMGUI_API void UpdatePlatformWindows(); // call in main loop. will call CreateWindow/ResizeWindow/etc. platform functions for each secondary viewport, and DestroyWindow for each inactive viewport. + IMGUI_API void RenderPlatformWindowsDefault(void* platform_render_arg = NULL, void* renderer_render_arg = NULL); // call in main loop. will call RenderWindow/SwapBuffers platform functions for each secondary viewport which doesn't have the ImGuiViewportFlags_Minimized flag set. May be reimplemented by user for custom rendering needs. + IMGUI_API void DestroyPlatformWindows(); // call DestroyWindow platform functions for all viewports. call from backend Shutdown() if you need to close platform windows before imgui shutdown. otherwise will be called by DestroyContext(). + IMGUI_API ImGuiViewport* FindViewportByID(ImGuiID id); // this is a helper for backends. + IMGUI_API ImGuiViewport* FindViewportByPlatformHandle(void* platform_handle); // this is a helper for backends. the type platform_handle is decided by the backend (e.g. HWND, MyWindow*, GLFWwindow* etc.) + } // namespace ImGui //----------------------------------------------------------------------------- @@ -1036,29 +1121,31 @@ enum ImGuiWindowFlags_ ImGuiWindowFlags_NoBringToFrontOnFocus = 1 << 13, // Disable bringing window to front when taking focus (e.g. clicking on it or programmatically giving it focus) ImGuiWindowFlags_AlwaysVerticalScrollbar= 1 << 14, // Always show vertical scrollbar (even if ContentSize.y < Size.y) ImGuiWindowFlags_AlwaysHorizontalScrollbar=1<< 15, // Always show horizontal scrollbar (even if ContentSize.x < Size.x) - ImGuiWindowFlags_NoNavInputs = 1 << 16, // No gamepad/keyboard navigation within the window - ImGuiWindowFlags_NoNavFocus = 1 << 17, // No focusing toward this window with gamepad/keyboard navigation (e.g. skipped by CTRL+TAB) + ImGuiWindowFlags_NoNavInputs = 1 << 16, // No keyboard/gamepad navigation within the window + ImGuiWindowFlags_NoNavFocus = 1 << 17, // No focusing toward this window with keyboard/gamepad navigation (e.g. skipped by CTRL+TAB) ImGuiWindowFlags_UnsavedDocument = 1 << 18, // Display a dot next to the title. When used in a tab/docking context, tab is selected when clicking the X + closure is not assumed (will wait for user to stop submitting the tab). Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar. + ImGuiWindowFlags_NoDocking = 1 << 19, // Disable docking of this window ImGuiWindowFlags_NoNav = ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, ImGuiWindowFlags_NoDecoration = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse, ImGuiWindowFlags_NoInputs = ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, // [Internal] - ImGuiWindowFlags_NavFlattened = 1 << 23, // [BETA] On child window: share focus scope, allow gamepad/keyboard navigation to cross over parent border to this child or between sibling child windows. ImGuiWindowFlags_ChildWindow = 1 << 24, // Don't use! For internal use by BeginChild() ImGuiWindowFlags_Tooltip = 1 << 25, // Don't use! For internal use by BeginTooltip() ImGuiWindowFlags_Popup = 1 << 26, // Don't use! For internal use by BeginPopup() ImGuiWindowFlags_Modal = 1 << 27, // Don't use! For internal use by BeginPopupModal() ImGuiWindowFlags_ChildMenu = 1 << 28, // Don't use! For internal use by BeginMenu() + ImGuiWindowFlags_DockNodeHost = 1 << 29, // Don't use! For internal use by Begin()/NewFrame() // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 30, // Obsoleted in 1.90: Use ImGuiChildFlags_AlwaysUseWindowPadding in BeginChild() call. + ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 30, // Obsoleted in 1.90.0: Use ImGuiChildFlags_AlwaysUseWindowPadding in BeginChild() call. + ImGuiWindowFlags_NavFlattened = 1 << 31, // Obsoleted in 1.90.9: Use ImGuiChildFlags_NavFlattened in BeginChild() call. #endif }; // Flags for ImGui::BeginChild() -// (Legacy: bit 0 must always correspond to ImGuiChildFlags_Border to be backward compatible with old API using 'bool border = false'. +// (Legacy: bit 0 must always correspond to ImGuiChildFlags_Borders to be backward compatible with old API using 'bool border = false'. // About using AutoResizeX/AutoResizeY flags: // - May be combined with SetNextWindowSizeConstraints() to set a min/max size for each axis (see "Demo->Child->Auto-resize with Constraints"). // - Size measurement for a given axis is only performed when the child window is within visible boundaries, or is just appearing. @@ -1069,7 +1156,7 @@ enum ImGuiWindowFlags_ enum ImGuiChildFlags_ { ImGuiChildFlags_None = 0, - ImGuiChildFlags_Border = 1 << 0, // Show an outer border and enable WindowPadding. (IMPORTANT: this is always == 1 == true for legacy reason) + ImGuiChildFlags_Borders = 1 << 0, // Show an outer border and enable WindowPadding. (IMPORTANT: this is always == 1 == true for legacy reason) ImGuiChildFlags_AlwaysUseWindowPadding = 1 << 1, // Pad with style.WindowPadding even if no border are drawn (no padding by default for non-bordered child windows because it makes more sense) ImGuiChildFlags_ResizeX = 1 << 2, // Allow resize from right border (layout direction). Enable .ini saving (unless ImGuiWindowFlags_NoSavedSettings passed to window flags) ImGuiChildFlags_ResizeY = 1 << 3, // Allow resize from bottom border (layout direction). " @@ -1077,6 +1164,25 @@ enum ImGuiChildFlags_ ImGuiChildFlags_AutoResizeY = 1 << 5, // Enable auto-resizing height. Read "IMPORTANT: Size measurement" details above. ImGuiChildFlags_AlwaysAutoResize = 1 << 6, // Combined with AutoResizeX/AutoResizeY. Always measure size even when child is hidden, always return true, always disable clipping optimization! NOT RECOMMENDED. ImGuiChildFlags_FrameStyle = 1 << 7, // Style the child window like a framed item: use FrameBg, FrameRounding, FrameBorderSize, FramePadding instead of ChildBg, ChildRounding, ChildBorderSize, WindowPadding. + ImGuiChildFlags_NavFlattened = 1 << 8, // [BETA] Share focus scope, allow keyboard/gamepad navigation to cross over parent border to this child or between sibling child windows. + + // Obsolete names +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiChildFlags_Border = ImGuiChildFlags_Borders, // Renamed in 1.91.1 (August 2024) for consistency. +#endif +}; + +// Flags for ImGui::PushItemFlag() +// (Those are shared by all items) +enum ImGuiItemFlags_ +{ + ImGuiItemFlags_None = 0, // (Default) + ImGuiItemFlags_NoTabStop = 1 << 0, // false // Disable keyboard tabbing. This is a "lighter" version of ImGuiItemFlags_NoNav. + ImGuiItemFlags_NoNav = 1 << 1, // false // Disable any form of focusing (keyboard/gamepad directional navigation and SetKeyboardFocusHere() calls). + ImGuiItemFlags_NoNavDefaultFocus = 1 << 2, // false // Disable item being a candidate for default focus (e.g. used by title bar items). + ImGuiItemFlags_ButtonRepeat = 1 << 3, // false // Any button-like behavior will have repeat mode enabled (based on io.KeyRepeatDelay and io.KeyRepeatRate values). Note that you can also call IsItemActive() after any button to tell if it is being held. + ImGuiItemFlags_AutoClosePopups = 1 << 4, // true // MenuItem()/Selectable() automatically close their parent popup window. + ImGuiItemFlags_AllowDuplicateId = 1 << 5, // false // Allow submitting an item with the same identifier as an item already submitted this frame without triggering a warning tooltip if io.ConfigDebugHighlightIdConflicts is set. }; // Flags for ImGui::InputText() @@ -1093,7 +1199,7 @@ enum ImGuiInputTextFlags_ // Inputs ImGuiInputTextFlags_AllowTabInput = 1 << 5, // Pressing TAB input a '\t' character into the text field - ImGuiInputTextFlags_EnterReturnsTrue = 1 << 6, // Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider looking at the IsItemDeactivatedAfterEdit() function. + ImGuiInputTextFlags_EnterReturnsTrue = 1 << 6, // Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider using IsItemDeactivatedAfterEdit() instead! ImGuiInputTextFlags_EscapeClearsAll = 1 << 7, // Escape key clears content if not empty, and deactivate otherwise (contrast to default behavior of Escape to revert) ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 8, // In multi-line mode, validate with Enter, add new line with Ctrl+Enter (default is opposite: validate with Ctrl+Enter, add line with Enter). @@ -1107,13 +1213,16 @@ enum ImGuiInputTextFlags_ ImGuiInputTextFlags_NoHorizontalScroll = 1 << 15, // Disable following the cursor horizontally ImGuiInputTextFlags_NoUndoRedo = 1 << 16, // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). + // Elide display / Alignment + ImGuiInputTextFlags_ElideLeft = 1 << 17, // When text doesn't fit, elide left side to ensure right side stays visible. Useful for path/filenames. Single-line only! + // Callback features - ImGuiInputTextFlags_CallbackCompletion = 1 << 17, // Callback on pressing TAB (for completion handling) - ImGuiInputTextFlags_CallbackHistory = 1 << 18, // Callback on pressing Up/Down arrows (for history handling) - ImGuiInputTextFlags_CallbackAlways = 1 << 19, // Callback on each iteration. User code may query cursor position, modify text buffer. - ImGuiInputTextFlags_CallbackCharFilter = 1 << 20, // Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in callback to discard. - ImGuiInputTextFlags_CallbackResize = 1 << 21, // Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. Notify when the string wants to be resized (for string types which hold a cache of their Size). You will be provided a new BufSize in the callback and NEED to honor it. (see misc/cpp/imgui_stdlib.h for an example of using this) - ImGuiInputTextFlags_CallbackEdit = 1 << 22, // Callback on any edit (note that InputText() already returns true on edit, the callback is useful mainly to manipulate the underlying buffer while focus is active) + ImGuiInputTextFlags_CallbackCompletion = 1 << 18, // Callback on pressing TAB (for completion handling) + ImGuiInputTextFlags_CallbackHistory = 1 << 19, // Callback on pressing Up/Down arrows (for history handling) + ImGuiInputTextFlags_CallbackAlways = 1 << 20, // Callback on each iteration. User code may query cursor position, modify text buffer. + ImGuiInputTextFlags_CallbackCharFilter = 1 << 21, // Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in callback to discard. + ImGuiInputTextFlags_CallbackResize = 1 << 22, // Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. Notify when the string wants to be resized (for string types which hold a cache of their Size). You will be provided a new BufSize in the callback and NEED to honor it. (see misc/cpp/imgui_stdlib.h for an example of using this) + ImGuiInputTextFlags_CallbackEdit = 1 << 23, // Callback on any edit (note that InputText() already returns true on edit, the callback is useful mainly to manipulate the underlying buffer while focus is active) // Obsolete names //ImGuiInputTextFlags_AlwaysInsertMode = ImGuiInputTextFlags_AlwaysOverwrite // [renamed in 1.82] name was not matching behavior @@ -1129,8 +1238,8 @@ enum ImGuiTreeNodeFlags_ ImGuiTreeNodeFlags_NoTreePushOnOpen = 1 << 3, // Don't do a TreePush() when open (e.g. for CollapsingHeader) = no extra indent nor pushing on ID stack ImGuiTreeNodeFlags_NoAutoOpenOnLog = 1 << 4, // Don't automatically and temporarily open node when Logging is active (by default logging will automatically open tree nodes) ImGuiTreeNodeFlags_DefaultOpen = 1 << 5, // Default node to be open - ImGuiTreeNodeFlags_OpenOnDoubleClick = 1 << 6, // Need double-click to open node - ImGuiTreeNodeFlags_OpenOnArrow = 1 << 7, // Only open when clicking on the arrow part. If ImGuiTreeNodeFlags_OpenOnDoubleClick is also set, single-click arrow or double-click all box to open. + ImGuiTreeNodeFlags_OpenOnDoubleClick = 1 << 6, // Open on double-click instead of simple click (default for multi-select unless any _OpenOnXXX behavior is set explicitly). Both behaviors may be combined. + ImGuiTreeNodeFlags_OpenOnArrow = 1 << 7, // Open when clicking on the arrow part (default for multi-select unless any _OpenOnXXX behavior is set explicitly). Both behaviors may be combined. ImGuiTreeNodeFlags_Leaf = 1 << 8, // No collapsing, no arrow (use as a convenience for leaf nodes). ImGuiTreeNodeFlags_Bullet = 1 << 9, // Display a bullet instead of arrow. IMPORTANT: node can still be marked open/close if you don't set the _Leaf flag! ImGuiTreeNodeFlags_FramePadding = 1 << 10, // Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. Equivalent to calling AlignTextToFramePadding() before the node. @@ -1176,14 +1285,16 @@ enum ImGuiPopupFlags_ enum ImGuiSelectableFlags_ { ImGuiSelectableFlags_None = 0, - ImGuiSelectableFlags_DontClosePopups = 1 << 0, // Clicking this doesn't close parent popup window + ImGuiSelectableFlags_NoAutoClosePopups = 1 << 0, // Clicking this doesn't close parent popup window (overrides ImGuiItemFlags_AutoClosePopups) ImGuiSelectableFlags_SpanAllColumns = 1 << 1, // Frame will span all columns of its container table (text will still fit in current column) ImGuiSelectableFlags_AllowDoubleClick = 1 << 2, // Generate press events on double clicks too ImGuiSelectableFlags_Disabled = 1 << 3, // Cannot be selected, display grayed out text ImGuiSelectableFlags_AllowOverlap = 1 << 4, // (WIP) Hit testing to allow subsequent widgets to overlap this one + ImGuiSelectableFlags_Highlight = 1 << 5, // Make the item be displayed as if it is hovered #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - ImGuiSelectableFlags_AllowItemOverlap = ImGuiSelectableFlags_AllowOverlap, // Renamed in 1.89.7 + ImGuiSelectableFlags_DontClosePopups = ImGuiSelectableFlags_NoAutoClosePopups, // Renamed in 1.91.0 + ImGuiSelectableFlags_AllowItemOverlap = ImGuiSelectableFlags_AllowOverlap, // Renamed in 1.89.7 #endif }; @@ -1212,8 +1323,9 @@ enum ImGuiTabBarFlags_ ImGuiTabBarFlags_NoCloseWithMiddleMouseButton = 1 << 3, // Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You may handle this behavior manually on user's side with if (IsItemHovered() && IsMouseClicked(2)) *p_open = false. ImGuiTabBarFlags_NoTabListScrollingButtons = 1 << 4, // Disable scrolling buttons (apply when fitting policy is ImGuiTabBarFlags_FittingPolicyScroll) ImGuiTabBarFlags_NoTooltip = 1 << 5, // Disable tooltips when hovering a tab - ImGuiTabBarFlags_FittingPolicyResizeDown = 1 << 6, // Resize tabs when they don't fit - ImGuiTabBarFlags_FittingPolicyScroll = 1 << 7, // Add scroll buttons when tabs don't fit + ImGuiTabBarFlags_DrawSelectedOverline = 1 << 6, // Draw selected overline markers over selected tab + ImGuiTabBarFlags_FittingPolicyResizeDown = 1 << 7, // Resize tabs when they don't fit + ImGuiTabBarFlags_FittingPolicyScroll = 1 << 8, // Add scroll buttons when tabs don't fit ImGuiTabBarFlags_FittingPolicyMask_ = ImGuiTabBarFlags_FittingPolicyResizeDown | ImGuiTabBarFlags_FittingPolicyScroll, ImGuiTabBarFlags_FittingPolicyDefault_ = ImGuiTabBarFlags_FittingPolicyResizeDown, }; @@ -1241,7 +1353,7 @@ enum ImGuiFocusedFlags_ ImGuiFocusedFlags_RootWindow = 1 << 1, // Test from root window (top most parent of the current hierarchy) ImGuiFocusedFlags_AnyWindow = 1 << 2, // Return true if any window is focused. Important: If you are trying to tell how to dispatch your low-level inputs, do NOT use this. Use 'io.WantCaptureMouse' instead! Please read the FAQ! ImGuiFocusedFlags_NoPopupHierarchy = 1 << 3, // Do not consider popup hierarchy (do not treat popup emitter as parent of popup) (when used with _ChildWindows or _RootWindow) - //ImGuiFocusedFlags_DockHierarchy = 1 << 4, // Consider docking hierarchy (treat dockspace host as parent of docked window) (when used with _ChildWindows or _RootWindow) + ImGuiFocusedFlags_DockHierarchy = 1 << 4, // Consider docking hierarchy (treat dockspace host as parent of docked window) (when used with _ChildWindows or _RootWindow) ImGuiFocusedFlags_RootAndChildWindows = ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows, }; @@ -1255,14 +1367,14 @@ enum ImGuiHoveredFlags_ ImGuiHoveredFlags_RootWindow = 1 << 1, // IsWindowHovered() only: Test from root window (top most parent of the current hierarchy) ImGuiHoveredFlags_AnyWindow = 1 << 2, // IsWindowHovered() only: Return true if any window is hovered ImGuiHoveredFlags_NoPopupHierarchy = 1 << 3, // IsWindowHovered() only: Do not consider popup hierarchy (do not treat popup emitter as parent of popup) (when used with _ChildWindows or _RootWindow) - //ImGuiHoveredFlags_DockHierarchy = 1 << 4, // IsWindowHovered() only: Consider docking hierarchy (treat dockspace host as parent of docked window) (when used with _ChildWindows or _RootWindow) + ImGuiHoveredFlags_DockHierarchy = 1 << 4, // IsWindowHovered() only: Consider docking hierarchy (treat dockspace host as parent of docked window) (when used with _ChildWindows or _RootWindow) ImGuiHoveredFlags_AllowWhenBlockedByPopup = 1 << 5, // Return true even if a popup window is normally blocking access to this item/window //ImGuiHoveredFlags_AllowWhenBlockedByModal = 1 << 6, // Return true even if a modal popup window is normally blocking access to this item/window. FIXME-TODO: Unavailable yet. ImGuiHoveredFlags_AllowWhenBlockedByActiveItem = 1 << 7, // Return true even if an active item is blocking access to this item/window. Useful for Drag and Drop patterns. ImGuiHoveredFlags_AllowWhenOverlappedByItem = 1 << 8, // IsItemHovered() only: Return true even if the item uses AllowOverlap mode and is overlapped by another hoverable item. ImGuiHoveredFlags_AllowWhenOverlappedByWindow = 1 << 9, // IsItemHovered() only: Return true even if the position is obstructed or overlapped by another window. ImGuiHoveredFlags_AllowWhenDisabled = 1 << 10, // IsItemHovered() only: Return true even if the item is disabled - ImGuiHoveredFlags_NoNavOverride = 1 << 11, // IsItemHovered() only: Disable using gamepad/keyboard navigation state when active, always query mouse + ImGuiHoveredFlags_NoNavOverride = 1 << 11, // IsItemHovered() only: Disable using keyboard/gamepad navigation state when active, always query mouse ImGuiHoveredFlags_AllowWhenOverlapped = ImGuiHoveredFlags_AllowWhenOverlappedByItem | ImGuiHoveredFlags_AllowWhenOverlappedByWindow, ImGuiHoveredFlags_RectOnly = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped, ImGuiHoveredFlags_RootAndChildWindows = ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows, @@ -1285,6 +1397,27 @@ enum ImGuiHoveredFlags_ ImGuiHoveredFlags_NoSharedDelay = 1 << 17, // IsItemHovered() only: Disable shared delay system where moving from one item to the next keeps the previous timer for a short time (standard for tooltips with long delays) }; +// Flags for ImGui::DockSpace(), shared/inherited by child nodes. +// (Some flags can be applied to individual nodes directly) +// FIXME-DOCK: Also see ImGuiDockNodeFlagsPrivate_ which may involve using the WIP and internal DockBuilder api. +enum ImGuiDockNodeFlags_ +{ + ImGuiDockNodeFlags_None = 0, + ImGuiDockNodeFlags_KeepAliveOnly = 1 << 0, // // Don't display the dockspace node but keep it alive. Windows docked into this dockspace node won't be undocked. + //ImGuiDockNodeFlags_NoCentralNode = 1 << 1, // // Disable Central Node (the node which can stay empty) + ImGuiDockNodeFlags_NoDockingOverCentralNode = 1 << 2, // // Disable docking over the Central Node, which will be always kept empty. + ImGuiDockNodeFlags_PassthruCentralNode = 1 << 3, // // Enable passthru dockspace: 1) DockSpace() will render a ImGuiCol_WindowBg background covering everything excepted the Central Node when empty. Meaning the host window should probably use SetNextWindowBgAlpha(0.0f) prior to Begin() when using this. 2) When Central Node is empty: let inputs pass-through + won't display a DockingEmptyBg background. See demo for details. + ImGuiDockNodeFlags_NoDockingSplit = 1 << 4, // // Disable other windows/nodes from splitting this node. + ImGuiDockNodeFlags_NoResize = 1 << 5, // Saved // Disable resizing node using the splitter/separators. Useful with programmatically setup dockspaces. + ImGuiDockNodeFlags_AutoHideTabBar = 1 << 6, // // Tab bar will automatically hide when there is a single window in the dock node. + ImGuiDockNodeFlags_NoUndocking = 1 << 7, // // Disable undocking this node. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiDockNodeFlags_NoSplit = ImGuiDockNodeFlags_NoDockingSplit, // Renamed in 1.90 + ImGuiDockNodeFlags_NoDockingInCentralNode = ImGuiDockNodeFlags_NoDockingOverCentralNode, // Renamed in 1.90 +#endif +}; + // Flags for ImGui::BeginDragDropSource(), ImGui::AcceptDragDropPayload() enum ImGuiDragDropFlags_ { @@ -1295,12 +1428,18 @@ enum ImGuiDragDropFlags_ ImGuiDragDropFlags_SourceNoHoldToOpenOthers = 1 << 2, // Disable the behavior that allows to open tree nodes and collapsing header by holding over them while dragging a source item. ImGuiDragDropFlags_SourceAllowNullID = 1 << 3, // Allow items such as Text(), Image() that have no unique identifier to be used as drag source, by manufacturing a temporary identifier based on their window-relative position. This is extremely unusual within the dear imgui ecosystem and so we made it explicit. ImGuiDragDropFlags_SourceExtern = 1 << 4, // External source (from outside of dear imgui), won't attempt to read current item/window info. Will always return true. Only one Extern source can be active simultaneously. - ImGuiDragDropFlags_SourceAutoExpirePayload = 1 << 5, // Automatically expire the payload if the source cease to be submitted (otherwise payloads are persisting while being dragged) + ImGuiDragDropFlags_PayloadAutoExpire = 1 << 5, // Automatically expire the payload if the source cease to be submitted (otherwise payloads are persisting while being dragged) + ImGuiDragDropFlags_PayloadNoCrossContext = 1 << 6, // Hint to specify that the payload may not be copied outside current dear imgui context. + ImGuiDragDropFlags_PayloadNoCrossProcess = 1 << 7, // Hint to specify that the payload may not be copied outside current process. // AcceptDragDropPayload() flags ImGuiDragDropFlags_AcceptBeforeDelivery = 1 << 10, // AcceptDragDropPayload() will returns true even before the mouse button is released. You can then call IsDelivery() to test if the payload needs to be delivered. ImGuiDragDropFlags_AcceptNoDrawDefaultRect = 1 << 11, // Do not draw the default highlight rectangle when hovering over target. ImGuiDragDropFlags_AcceptNoPreviewTooltip = 1 << 12, // Request hiding the BeginDragDropSource tooltip from the BeginDragDropTarget site. ImGuiDragDropFlags_AcceptPeekOnly = ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect, // For peeking ahead and inspecting the payload before delivery. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiDragDropFlags_SourceAutoExpirePayload = ImGuiDragDropFlags_PayloadAutoExpire, // Renamed in 1.90.9 +#endif }; // Standard Drag and Drop payload types. You can define you own payload types using short strings. Types starting with '_' are defined by Dear ImGui. @@ -1320,6 +1459,7 @@ enum ImGuiDataType_ ImGuiDataType_U64, // unsigned long long / unsigned __int64 ImGuiDataType_Float, // float ImGuiDataType_Double, // double + ImGuiDataType_Bool, // bool (provided for user convenience, not supported by scalar widgets) ImGuiDataType_COUNT }; @@ -1342,21 +1482,18 @@ enum ImGuiSortDirection : ImU8 ImGuiSortDirection_Descending = 2 // Descending = 9->0, Z->A etc. }; -// Since 1.90, defining IMGUI_DISABLE_OBSOLETE_FUNCTIONS automatically defines IMGUI_DISABLE_OBSOLETE_KEYIO as well. -#if defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && !defined(IMGUI_DISABLE_OBSOLETE_KEYIO) -#define IMGUI_DISABLE_OBSOLETE_KEYIO -#endif - // A key identifier (ImGuiKey_XXX or ImGuiMod_XXX value): can represent Keyboard, Mouse and Gamepad values. -// All our named keys are >= 512. Keys value 0 to 511 are left unused as legacy native/opaque key values (< 1.87). -// Since >= 1.89 we increased typing (went from int to enum), some legacy code may need a cast to ImGuiKey. -// Read details about the 1.87 and 1.89 transition : https://github.com/ocornut/imgui/issues/4921 +// All our named keys are >= 512. Keys value 0 to 511 are left unused and were legacy native/opaque key values (< 1.87). +// Support for legacy keys was completely removed in 1.91.5. +// Read details about the 1.87+ transition : https://github.com/ocornut/imgui/issues/4921 // Note that "Keys" related to physical keys and are not the same concept as input "Characters", the later are submitted via io.AddInputCharacter(). // The keyboard key enum values are named after the keys on a standard US keyboard, and on other keyboard types the keys reported may not match the keycaps. enum ImGuiKey : int { // Keyboard ImGuiKey_None = 0, + ImGuiKey_NamedKey_BEGIN = 512, // First valid key value (other than 0) + ImGuiKey_Tab = 512, // == ImGuiKey_NamedKey_BEGIN ImGuiKey_LeftArrow, ImGuiKey_RightArrow, @@ -1444,7 +1581,7 @@ enum ImGuiKey : int // [Internal] Reserved for mod storage ImGuiKey_ReservedForModCtrl, ImGuiKey_ReservedForModShift, ImGuiKey_ReservedForModAlt, ImGuiKey_ReservedForModSuper, - ImGuiKey_COUNT, + ImGuiKey_NamedKey_END, // Keyboard Modifiers (explicitly submitted by backend via AddKeyEvent() calls) // - This is mirroring the data also written to io.KeyCtrl, io.KeyShift, io.KeyAlt, io.KeySuper, in a format allowing @@ -1462,21 +1599,13 @@ enum ImGuiKey : int ImGuiMod_Super = 1 << 15, // Windows/Super (non-macOS), Ctrl (macOS) ImGuiMod_Mask_ = 0xF000, // 4-bits - // [Internal] Prior to 1.87 we required user to fill io.KeysDown[512] using their own native index + the io.KeyMap[] array. - // We are ditching this method but keeping a legacy path for user code doing e.g. IsKeyPressed(MY_NATIVE_KEY_CODE) - // If you need to iterate all keys (for e.g. an input mapper) you may use ImGuiKey_NamedKey_BEGIN..ImGuiKey_NamedKey_END. - ImGuiKey_NamedKey_BEGIN = 512, - ImGuiKey_NamedKey_END = ImGuiKey_COUNT, + // [Internal] If you need to iterate all keys (for e.g. an input mapper) you may use ImGuiKey_NamedKey_BEGIN..ImGuiKey_NamedKey_END. ImGuiKey_NamedKey_COUNT = ImGuiKey_NamedKey_END - ImGuiKey_NamedKey_BEGIN, -#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO - ImGuiKey_KeysData_SIZE = ImGuiKey_NamedKey_COUNT, // Size of KeysData[]: only hold named keys - ImGuiKey_KeysData_OFFSET = ImGuiKey_NamedKey_BEGIN, // Accesses to io.KeysData[] must use (key - ImGuiKey_KeysData_OFFSET) index. -#else - ImGuiKey_KeysData_SIZE = ImGuiKey_COUNT, // Size of KeysData[]: hold legacy 0..512 keycodes + named keys - ImGuiKey_KeysData_OFFSET = 0, // Accesses to io.KeysData[] must use (key - ImGuiKey_KeysData_OFFSET) index. -#endif + //ImGuiKey_KeysData_SIZE = ImGuiKey_NamedKey_COUNT, // Size of KeysData[]: only hold named keys + //ImGuiKey_KeysData_OFFSET = ImGuiKey_NamedKey_BEGIN, // Accesses to io.KeysData[] must use (key - ImGuiKey_NamedKey_BEGIN) index. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiKey_COUNT = ImGuiKey_NamedKey_END, // Obsoleted in 1.91.5 because it was extremely misleading (since named keys don't start at 0 anymore) ImGuiMod_Shortcut = ImGuiMod_Ctrl, // Removed in 1.90.7, you can now simply use ImGuiMod_Ctrl ImGuiKey_ModCtrl = ImGuiMod_Ctrl, ImGuiKey_ModShift = ImGuiMod_Shift, ImGuiKey_ModAlt = ImGuiMod_Alt, ImGuiKey_ModSuper = ImGuiMod_Super, // Renamed in 1.89 //ImGuiKey_KeyPadEnter = ImGuiKey_KeypadEnter, // Renamed in 1.87 @@ -1508,32 +1637,33 @@ enum ImGuiInputFlags_ ImGuiInputFlags_Tooltip = 1 << 18, // Automatically display a tooltip when hovering item [BETA] Unsure of right api (opt-in/opt-out) }; -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO -// OBSOLETED in 1.88 (from July 2022): ImGuiNavInput and io.NavInputs[]. -// Official backends between 1.60 and 1.86: will keep working and feed gamepad inputs as long as IMGUI_DISABLE_OBSOLETE_KEYIO is not set. -// Custom backends: feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums. -enum ImGuiNavInput -{ - ImGuiNavInput_Activate, ImGuiNavInput_Cancel, ImGuiNavInput_Input, ImGuiNavInput_Menu, ImGuiNavInput_DpadLeft, ImGuiNavInput_DpadRight, ImGuiNavInput_DpadUp, ImGuiNavInput_DpadDown, - ImGuiNavInput_LStickLeft, ImGuiNavInput_LStickRight, ImGuiNavInput_LStickUp, ImGuiNavInput_LStickDown, ImGuiNavInput_FocusPrev, ImGuiNavInput_FocusNext, ImGuiNavInput_TweakSlow, ImGuiNavInput_TweakFast, - ImGuiNavInput_COUNT, -}; -#endif - // Configuration flags stored in io.ConfigFlags. Set by user/application. enum ImGuiConfigFlags_ { ImGuiConfigFlags_None = 0, ImGuiConfigFlags_NavEnableKeyboard = 1 << 0, // Master keyboard navigation enable flag. Enable full Tabbing + directional arrows + space/enter to activate. ImGuiConfigFlags_NavEnableGamepad = 1 << 1, // Master gamepad navigation enable flag. Backend also needs to set ImGuiBackendFlags_HasGamepad. - ImGuiConfigFlags_NavEnableSetMousePos = 1 << 2, // Instruct navigation to move the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is awkward. Will update io.MousePos and set io.WantSetMousePos=true. If enabled you MUST honor io.WantSetMousePos requests in your backend, otherwise ImGui will react as if the mouse is jumping around back and forth. - ImGuiConfigFlags_NavNoCaptureKeyboard = 1 << 3, // Instruct navigation to not set the io.WantCaptureKeyboard flag when io.NavActive is set. - ImGuiConfigFlags_NoMouse = 1 << 4, // Instruct imgui to clear mouse position/buttons in NewFrame(). This allows ignoring the mouse information set by the backend. + ImGuiConfigFlags_NoMouse = 1 << 4, // Instruct dear imgui to disable mouse inputs and interactions. ImGuiConfigFlags_NoMouseCursorChange = 1 << 5, // Instruct backend to not alter mouse cursor shape and visibility. Use if the backend cursor changes are interfering with yours and you don't want to use SetMouseCursor() to change mouse cursor. You may want to honor requests from imgui by reading GetMouseCursor() yourself instead. + ImGuiConfigFlags_NoKeyboard = 1 << 6, // Instruct dear imgui to disable keyboard inputs and interactions. This is done by ignoring keyboard events and clearing existing states. + + // [BETA] Docking + ImGuiConfigFlags_DockingEnable = 1 << 7, // Docking enable flags. + + // [BETA] Viewports + // When using viewports it is recommended that your default value for ImGuiCol_WindowBg is opaque (Alpha=1.0) so transition to a viewport won't be noticeable. + ImGuiConfigFlags_ViewportsEnable = 1 << 10, // Viewport enable flags (require both ImGuiBackendFlags_PlatformHasViewports + ImGuiBackendFlags_RendererHasViewports set by the respective backends) + ImGuiConfigFlags_DpiEnableScaleViewports= 1 << 14, // [BETA: Don't use] FIXME-DPI: Reposition and resize imgui windows when the DpiScale of a viewport changed (mostly useful for the main viewport hosting other window). Note that resizing the main window itself is up to your application. + ImGuiConfigFlags_DpiEnableScaleFonts = 1 << 15, // [BETA: Don't use] FIXME-DPI: Request bitmap-scaled fonts to match DpiScale. This is a very low-quality workaround. The correct way to handle DPI is _currently_ to replace the atlas and/or fonts in the Platform_OnChangedViewport callback, but this is all early work in progress. // User storage (to allow your backend/engine to communicate to code that may be shared between multiple projects. Those flags are NOT used by core Dear ImGui) ImGuiConfigFlags_IsSRGB = 1 << 20, // Application is SRGB-aware. ImGuiConfigFlags_IsTouchScreen = 1 << 21, // Application is using a touch screen instead of a mouse. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiConfigFlags_NavEnableSetMousePos = 1 << 2, // [moved/renamed in 1.91.4] -> use bool io.ConfigNavMoveSetMousePos + ImGuiConfigFlags_NavNoCaptureKeyboard = 1 << 3, // [moved/renamed in 1.91.4] -> use bool io.ConfigNavCaptureKeyboard +#endif }; // Backend capabilities flags stored in io.BackendFlags. Set by imgui_impl_xxx or custom backend. @@ -1542,8 +1672,13 @@ enum ImGuiBackendFlags_ ImGuiBackendFlags_None = 0, ImGuiBackendFlags_HasGamepad = 1 << 0, // Backend Platform supports gamepad and currently has one connected. ImGuiBackendFlags_HasMouseCursors = 1 << 1, // Backend Platform supports honoring GetMouseCursor() value to change the OS cursor shape. - ImGuiBackendFlags_HasSetMousePos = 1 << 2, // Backend Platform supports io.WantSetMousePos requests to reposition the OS mouse position (only used if ImGuiConfigFlags_NavEnableSetMousePos is set). + ImGuiBackendFlags_HasSetMousePos = 1 << 2, // Backend Platform supports io.WantSetMousePos requests to reposition the OS mouse position (only used if io.ConfigNavMoveSetMousePos is set). ImGuiBackendFlags_RendererHasVtxOffset = 1 << 3, // Backend Renderer supports ImDrawCmd::VtxOffset. This enables output of large meshes (64K+ vertices) while still using 16-bit indices. + + // [BETA] Viewports + ImGuiBackendFlags_PlatformHasViewports = 1 << 10, // Backend Platform supports multiple viewports. + ImGuiBackendFlags_HasMouseHoveredViewport=1 << 11, // Backend Platform supports calling io.AddMouseViewportEvent() with the viewport under the mouse. IF POSSIBLE, ignore viewports with the ImGuiViewportFlags_NoInputs flag (Win32 backend, GLFW 3.30+ backend can do this, SDL backend cannot). If this cannot be done, Dear ImGui needs to use a flawed heuristic to find the viewport under. + ImGuiBackendFlags_RendererHasViewports = 1 << 12, // Backend Renderer supports multiple viewports. }; // Enumeration for PushStyleColor() / PopStyleColor() @@ -1582,11 +1717,15 @@ enum ImGuiCol_ ImGuiCol_ResizeGrip, // Resize grip in lower-right and lower-left corners of windows. ImGuiCol_ResizeGripHovered, ImGuiCol_ResizeGripActive, - ImGuiCol_Tab, // TabItem in a TabBar - ImGuiCol_TabHovered, - ImGuiCol_TabActive, - ImGuiCol_TabUnfocused, - ImGuiCol_TabUnfocusedActive, + ImGuiCol_TabHovered, // Tab background, when hovered + ImGuiCol_Tab, // Tab background, when tab-bar is focused & tab is unselected + ImGuiCol_TabSelected, // Tab background, when tab-bar is focused & tab is selected + ImGuiCol_TabSelectedOverline, // Tab horizontal overline, when tab-bar is focused & tab is selected + ImGuiCol_TabDimmed, // Tab background, when tab-bar is unfocused & tab is unselected + ImGuiCol_TabDimmedSelected, // Tab background, when tab-bar is unfocused & tab is selected + ImGuiCol_TabDimmedSelectedOverline,//..horizontal overline, when tab-bar is unfocused & tab is selected + ImGuiCol_DockingPreview, // Preview overlay color when about to docking something + ImGuiCol_DockingEmptyBg, // Background color for empty node (e.g. CentralNode with no window docked into it) ImGuiCol_PlotLines, ImGuiCol_PlotLinesHovered, ImGuiCol_PlotHistogram, @@ -1596,13 +1735,21 @@ enum ImGuiCol_ ImGuiCol_TableBorderLight, // Table inner borders (prefer using Alpha=1.0 here) ImGuiCol_TableRowBg, // Table row background (even rows) ImGuiCol_TableRowBgAlt, // Table row background (odd rows) + ImGuiCol_TextLink, // Hyperlink color ImGuiCol_TextSelectedBg, ImGuiCol_DragDropTarget, // Rectangle highlighting a drop target - ImGuiCol_NavHighlight, // Gamepad/keyboard: current highlighted item + ImGuiCol_NavCursor, // Color of keyboard/gamepad navigation cursor/rectangle, when visible ImGuiCol_NavWindowingHighlight, // Highlight window when using CTRL+TAB ImGuiCol_NavWindowingDimBg, // Darken/colorize entire screen behind the CTRL+TAB window list, when active ImGuiCol_ModalWindowDimBg, // Darken/colorize entire screen behind a modal window, when one is active - ImGuiCol_COUNT + ImGuiCol_COUNT, + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiCol_TabActive = ImGuiCol_TabSelected, // [renamed in 1.90.9] + ImGuiCol_TabUnfocused = ImGuiCol_TabDimmed, // [renamed in 1.90.9] + ImGuiCol_TabUnfocusedActive = ImGuiCol_TabDimmedSelected, // [renamed in 1.90.9] + ImGuiCol_NavHighlight = ImGuiCol_NavCursor, // [renamed in 1.91.4] +#endif }; // Enumeration for PushStyleVar() / PopStyleVar() to temporarily modify the ImGuiStyle structure. @@ -1641,13 +1788,15 @@ enum ImGuiStyleVar_ ImGuiStyleVar_TabRounding, // float TabRounding ImGuiStyleVar_TabBorderSize, // float TabBorderSize ImGuiStyleVar_TabBarBorderSize, // float TabBarBorderSize - ImGuiStyleVar_TableAngledHeadersAngle, // float TableAngledHeadersAngle - ImGuiStyleVar_TableAngledHeadersTextAlign,// ImVec2 TableAngledHeadersTextAlign + ImGuiStyleVar_TabBarOverlineSize, // float TabBarOverlineSize + ImGuiStyleVar_TableAngledHeadersAngle, // float TableAngledHeadersAngle + ImGuiStyleVar_TableAngledHeadersTextAlign,// ImVec2 TableAngledHeadersTextAlign ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign ImGuiStyleVar_SelectableTextAlign, // ImVec2 SelectableTextAlign - ImGuiStyleVar_SeparatorTextBorderSize, // float SeparatorTextBorderSize + ImGuiStyleVar_SeparatorTextBorderSize, // float SeparatorTextBorderSize ImGuiStyleVar_SeparatorTextAlign, // ImVec2 SeparatorTextAlign ImGuiStyleVar_SeparatorTextPadding, // ImVec2 SeparatorTextPadding + ImGuiStyleVar_DockingSeparatorSize, // float DockingSeparatorSize ImGuiStyleVar_COUNT }; @@ -1659,7 +1808,7 @@ enum ImGuiButtonFlags_ ImGuiButtonFlags_MouseButtonRight = 1 << 1, // React on right mouse button ImGuiButtonFlags_MouseButtonMiddle = 1 << 2, // React on center mouse button ImGuiButtonFlags_MouseButtonMask_ = ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight | ImGuiButtonFlags_MouseButtonMiddle, // [Internal] - //ImGuiButtonFlags_MouseButtonDefault_ = ImGuiButtonFlags_MouseButtonLeft, + ImGuiButtonFlags_EnableNav = 1 << 3, // InvisibleButton(): do not disable navigation/tabbing. Otherwise disabled by default. }; // Flags for ColorEdit3() / ColorEdit4() / ColorPicker3() / ColorPicker4() / ColorButton() @@ -1708,18 +1857,18 @@ enum ImGuiColorEditFlags_ // Flags for DragFloat(), DragInt(), SliderFloat(), SliderInt() etc. // We use the same sets of flags for DragXXX() and SliderXXX() functions as the features are the same and it makes it easier to swap them. -// (Those are per-item flags. There are shared flags in ImGuiIO: io.ConfigDragClickToInputText) +// (Those are per-item flags. There is shared behavior flag too: ImGuiIO: io.ConfigDragClickToInputText) enum ImGuiSliderFlags_ { - ImGuiSliderFlags_None = 0, - ImGuiSliderFlags_AlwaysClamp = 1 << 4, // Clamp value to min/max bounds when input manually with CTRL+Click. By default CTRL+Click allows going out of bounds. - ImGuiSliderFlags_Logarithmic = 1 << 5, // Make the widget logarithmic (linear otherwise). Consider using ImGuiSliderFlags_NoRoundToFormat with this if using a format-string with small amount of digits. - ImGuiSliderFlags_NoRoundToFormat = 1 << 6, // Disable rounding underlying value to match precision of the display format string (e.g. %.3f values are rounded to those 3 digits) - ImGuiSliderFlags_NoInput = 1 << 7, // Disable CTRL+Click or Enter key allowing to input text directly into the widget - ImGuiSliderFlags_InvalidMask_ = 0x7000000F, // [Internal] We treat using those bits as being potentially a 'float power' argument from the previous API that has got miscast to this enum, and will trigger an assert if needed. - - // Obsolete names - //ImGuiSliderFlags_ClampOnInput = ImGuiSliderFlags_AlwaysClamp, // [renamed in 1.79] + ImGuiSliderFlags_None = 0, + ImGuiSliderFlags_Logarithmic = 1 << 5, // Make the widget logarithmic (linear otherwise). Consider using ImGuiSliderFlags_NoRoundToFormat with this if using a format-string with small amount of digits. + ImGuiSliderFlags_NoRoundToFormat = 1 << 6, // Disable rounding underlying value to match precision of the display format string (e.g. %.3f values are rounded to those 3 digits). + ImGuiSliderFlags_NoInput = 1 << 7, // Disable CTRL+Click or Enter key allowing to input text directly into the widget. + ImGuiSliderFlags_WrapAround = 1 << 8, // Enable wrapping around from max to min and from min to max. Only supported by DragXXX() functions for now. + ImGuiSliderFlags_ClampOnInput = 1 << 9, // Clamp value to min/max bounds when input manually with CTRL+Click. By default CTRL+Click allows going out of bounds. + ImGuiSliderFlags_ClampZeroRange = 1 << 10, // Clamp even if min==max==0.0f. Otherwise due to legacy reason DragXXX functions don't clamp with those values. When your clamping limits are dynamic you almost always want to use it. + ImGuiSliderFlags_AlwaysClamp = ImGuiSliderFlags_ClampOnInput | ImGuiSliderFlags_ClampZeroRange, + ImGuiSliderFlags_InvalidMask_ = 0x7000000F, // [Internal] We treat using those bits as being potentially a 'float power' argument from the previous API that has got miscast to this enum, and will trigger an assert if needed. }; // Identify a mouse button. @@ -1868,7 +2017,7 @@ enum ImGuiTableColumnFlags_ ImGuiTableColumnFlags_NoSort = 1 << 9, // Disable ability to sort on this field (even if ImGuiTableFlags_Sortable is set on the table). ImGuiTableColumnFlags_NoSortAscending = 1 << 10, // Disable ability to sort in the ascending direction. ImGuiTableColumnFlags_NoSortDescending = 1 << 11, // Disable ability to sort in the descending direction. - ImGuiTableColumnFlags_NoHeaderLabel = 1 << 12, // TableHeadersRow() will not submit horizontal label for this column. Convenient for some small columns. Name will still appear in context menu or in angled headers. + ImGuiTableColumnFlags_NoHeaderLabel = 1 << 12, // TableHeadersRow() will submit an empty label for this column. Convenient for some small columns. Name will still appear in context menu or in angled headers. You may append into this cell by calling TableSetColumnIndex() right after the TableHeadersRow() call. ImGuiTableColumnFlags_NoHeaderWidth = 1 << 13, // Disable header text width contribution to automatic column width. ImGuiTableColumnFlags_PreferSortAscending = 1 << 14, // Make the initial sort direction Ascending when first sorting on this column (default). ImGuiTableColumnFlags_PreferSortDescending = 1 << 15, // Make the initial sort direction Descending when first sorting on this column. @@ -1938,32 +2087,34 @@ struct ImGuiTableColumnSortSpecs }; //----------------------------------------------------------------------------- -// [SECTION] Helpers: Memory allocations macros, ImVector<> +// [SECTION] Helpers: Debug log, memory allocations macros, ImVector<> //----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// Debug Logging into ShowDebugLogWindow(), tty and more. +//----------------------------------------------------------------------------- + +#ifndef IMGUI_DISABLE_DEBUG_TOOLS +#define IMGUI_DEBUG_LOG(...) ImGui::DebugLog(__VA_ARGS__) +#else +#define IMGUI_DEBUG_LOG(...) ((void)0) +#endif + //----------------------------------------------------------------------------- // IM_MALLOC(), IM_FREE(), IM_NEW(), IM_PLACEMENT_NEW(), IM_DELETE() // We call C++ constructor on own allocated memory via the placement "new(ptr) Type()" syntax. // Defining a custom placement new() with a custom parameter allows us to bypass including which on some platforms complains when user has disabled exceptions. //----------------------------------------------------------------------------- -#ifdef _WIN32 -#undef new -#endif - struct ImNewWrapper {}; inline void* operator new(size_t, ImNewWrapper, void* ptr) { return ptr; } inline void operator delete(void*, ImNewWrapper, void*) {} // This is only required so we can use the symmetrical new() #define IM_ALLOC(_SIZE) ImGui::MemAlloc(_SIZE) #define IM_FREE(_PTR) ImGui::MemFree(_PTR) -#define IM_PLACEMENT_NEW(_PTR) new(ImNewWrapper{}, _PTR) -#define IM_NEW(_TYPE) new(ImNewWrapper{}, ImGui::MemAlloc(sizeof(_TYPE))) _TYPE +#define IM_PLACEMENT_NEW(_PTR) new(ImNewWrapper(), _PTR) +#define IM_NEW(_TYPE) new(ImNewWrapper(), ImGui::MemAlloc(sizeof(_TYPE))) _TYPE template void IM_DELETE(T* p) { if (p) { p->~T(); ImGui::MemFree(p); } } -#ifdef _WIN32 -#include "Common/DbgNew.h" -#endif - //----------------------------------------------------------------------------- // ImVector<> // Lightweight std::vector<>-like class to avoid dragging dependencies (also, some implementations of STL with debug enabled are absurdly slow, we bypass it so our code runs fast in debug). @@ -2081,16 +2232,18 @@ struct ImGuiStyle float TabBorderSize; // Thickness of border around tabs. float TabMinWidthForCloseButton; // Minimum width for close button to appear on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected. float TabBarBorderSize; // Thickness of tab-bar separator, which takes on the tab active color to denote focus. + float TabBarOverlineSize; // Thickness of tab-bar overline, which highlights the selected tab-bar. float TableAngledHeadersAngle; // Angle of angled headers (supported values range from -50.0f degrees to +50.0f degrees). ImVec2 TableAngledHeadersTextAlign;// Alignment of angled headers within the cell ImGuiDir ColorButtonPosition; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f, 0.5f) (centered). ImVec2 SelectableTextAlign; // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. - float SeparatorTextBorderSize; // Thickkness of border in SeparatorText() + float SeparatorTextBorderSize; // Thickness of border in SeparatorText() ImVec2 SeparatorTextAlign; // Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center). ImVec2 SeparatorTextPadding; // Horizontal offset of text from each edge of the separator + spacing on other axis. Generally small values. .y is recommended to be == FramePadding.y. ImVec2 DisplayWindowPadding; // Apply to regular windows: amount which we enforce to keep visible when moving near edges of your screen. ImVec2 DisplaySafeAreaPadding; // Apply to every windows, menus, popups, tooltips: amount where we avoid displaying contents. Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured). + float DockingSeparatorSize; // Thickness of resizing border between docked windows float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). We apply per-monitor DPI scaling over this scale. May be removed later. bool AntiAliasedLines; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). bool AntiAliasedLinesUseTex; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). Latched at the beginning of the frame (copied to ImDrawList). @@ -2120,6 +2273,8 @@ struct ImGuiStyle // - initialization: backends and user code writes to ImGuiIO. // - main loop: backends writes to ImGuiIO, user code and imgui code reads from ImGuiIO. //----------------------------------------------------------------------------- +// Also see ImGui::GetPlatformIO() and ImGuiPlatformIO struct for OS/platform related functions: clipboard, IME etc. +//----------------------------------------------------------------------------- // [Internal] Storage used by IsKeyDown(), IsKeyPressed() etc functions. // If prior to 1.87 you used io.KeysDownDuration[] (which was marked as internal), you should use GetKeyData(key)->DownDuration and *NOT* io.KeysData[key]->DownDuration. @@ -2137,7 +2292,7 @@ struct ImGuiIO // Configuration // Default value //------------------------------------------------------------------ - ImGuiConfigFlags ConfigFlags; // = 0 // See ImGuiConfigFlags_ enum. Set by user/application. Gamepad/keyboard navigation options, etc. + ImGuiConfigFlags ConfigFlags; // = 0 // See ImGuiConfigFlags_ enum. Set by user/application. Keyboard/Gamepad navigation options, etc. ImGuiBackendFlags BackendFlags; // = 0 // See ImGuiBackendFlags_ enum. Set by backend (imgui_impl_xxx files or custom backend) to communicate features supported by the backend. ImVec2 DisplaySize; // // Main display size, in pixels (generally == GetMainViewport()->Size). May change every frame. float DeltaTime; // = 1.0f/60.0f // Time elapsed since last frame, in seconds. May change every frame. @@ -2146,21 +2301,46 @@ struct ImGuiIO const char* LogFilename; // = "imgui_log.txt"// Path to .log file (default parameter to ImGui::LogToFile when no file is specified). void* UserData; // = NULL // Store your own data. + // Font system ImFontAtlas*Fonts; // // Font atlas: load, rasterize and pack one or more fonts into a single texture. float FontGlobalScale; // = 1.0f // Global scale all fonts - bool FontAllowUserScaling; // = false // Allow user scaling text of individual window with CTRL+Wheel. + bool FontAllowUserScaling; // = false // [OBSOLETE] Allow user scaling text of individual window with CTRL+Wheel. ImFont* FontDefault; // = NULL // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0]. ImVec2 DisplayFramebufferScale; // = (1, 1) // For retina display or other situations where window coordinates are different from framebuffer coordinates. This generally ends up in ImDrawData::FramebufferScale. + // Keyboard/Gamepad Navigation options + bool ConfigNavSwapGamepadButtons; // = false // Swap Activate<>Cancel (A<>B) buttons, matching typical "Nintendo/Japanese style" gamepad layout. + bool ConfigNavMoveSetMousePos; // = false // Directional/tabbing navigation teleports the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is difficult. Will update io.MousePos and set io.WantSetMousePos=true. + bool ConfigNavCaptureKeyboard; // = true // Sets io.WantCaptureKeyboard when io.NavActive is set. + bool ConfigNavEscapeClearFocusItem; // = true // Pressing Escape can clear focused item + navigation id/highlight. Set to false if you want to always keep highlight on. + bool ConfigNavEscapeClearFocusWindow;// = false // Pressing Escape can clear focused window as well (super set of io.ConfigNavEscapeClearFocusItem). + bool ConfigNavCursorVisibleAuto; // = true // Using directional navigation key makes the cursor visible. Mouse click hides the cursor. + bool ConfigNavCursorVisibleAlways; // = false // Navigation cursor is always visible. + + // Docking options (when ImGuiConfigFlags_DockingEnable is set) + bool ConfigDockingNoSplit; // = false // Simplified docking mode: disable window splitting, so docking is limited to merging multiple windows together into tab-bars. + bool ConfigDockingWithShift; // = false // Enable docking with holding Shift key (reduce visual noise, allows dropping in wider space) + bool ConfigDockingAlwaysTabBar; // = false // [BETA] [FIXME: This currently creates regression with auto-sizing and general overhead] Make every single floating window display within a docking node. + bool ConfigDockingTransparentPayload;// = false // [BETA] Make window or viewport transparent when docking and only display docking boxes on the target viewport. Useful if rendering of multiple viewport cannot be synced. Best used with ConfigViewportsNoAutoMerge. + + // Viewport options (when ImGuiConfigFlags_ViewportsEnable is set) + bool ConfigViewportsNoAutoMerge; // = false; // Set to make all floating imgui windows always create their own viewport. Otherwise, they are merged into the main host viewports when overlapping it. May also set ImGuiViewportFlags_NoAutoMerge on individual viewport. + bool ConfigViewportsNoTaskBarIcon; // = false // Disable default OS task bar icon flag for secondary viewports. When a viewport doesn't want a task bar icon, ImGuiViewportFlags_NoTaskBarIcon will be set on it. + bool ConfigViewportsNoDecoration; // = true // Disable default OS window decoration flag for secondary viewports. When a viewport doesn't want window decorations, ImGuiViewportFlags_NoDecoration will be set on it. Enabling decoration can create subsequent issues at OS levels (e.g. minimum window size). + bool ConfigViewportsNoDefaultParent; // = false // Disable default OS parenting to main viewport for secondary viewports. By default, viewports are marked with ParentViewportId = , expecting the platform backend to setup a parent/child relationship between the OS windows (some backend may ignore this). Set to true if you want the default to be 0, then all viewports will be top-level OS windows. + // Miscellaneous options + // (you can visualize and interact with all options in 'Demo->Configuration') bool MouseDrawCursor; // = false // Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). Cannot be easily renamed to 'io.ConfigXXX' because this is frequently used by backend implementations. bool ConfigMacOSXBehaviors; // = defined(__APPLE__) // Swap Cmd<>Ctrl keys + OS X style text editing cursor movement using Alt instead of Ctrl, Shortcuts using Cmd/Super instead of Ctrl, Line/Text Start and End using Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting whole text, Multi-selection in lists uses Cmd/Super instead of Ctrl. bool ConfigInputTrickleEventQueue; // = true // Enable input queue trickling: some types of events submitted during the same frame (e.g. button down + up) will be spread over multiple frames, improving interactions with low framerates. bool ConfigInputTextCursorBlink; // = true // Enable blinking cursor (optional as some users consider it to be distracting). bool ConfigInputTextEnterKeepActive; // = false // [BETA] Pressing Enter will keep item active and select contents (single-line only). bool ConfigDragClickToInputText; // = false // [BETA] Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving). Not desirable on devices without a keyboard. - bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag) - bool ConfigWindowsMoveFromTitleBarOnly; // = false // Enable allowing to move windows only when clicking on their title bar. Does not apply to windows without a title bar. + bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires ImGuiBackendFlags_HasMouseCursors for better mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag) + bool ConfigWindowsMoveFromTitleBarOnly; // = false // Enable allowing to move windows only when clicking on their title bar. Does not apply to windows without a title bar. + bool ConfigWindowsCopyContentsWithCtrlC; // = false // [EXPERIMENTAL] CTRL+C copy the contents of focused window into the clipboard. Experimental because: (1) has known issues with nested Begin/End pairs (2) text output quality varies (3) text output is in submission order rather than spatial order. + bool ConfigScrollbarScrollByPage; // = true // Enable scrolling page by page when clicking outside the scrollbar grab. When disabled, always scroll to clicked location. When enabled, Shift+Click scrolls to clicked location. float ConfigMemoryCompactTimer; // = 60.0f // Timer (in seconds) to free transient windows/tables memory buffers when unused. Set to -1.0f to disable. // Inputs Behaviors @@ -2175,12 +2355,37 @@ struct ImGuiIO // Debug options //------------------------------------------------------------------ + // Options to configure Error Handling and how we handle recoverable errors [EXPERIMENTAL] + // - Error recovery is provided as a way to facilitate: + // - Recovery after a programming error (native code or scripting language - the later tends to facilitate iterating on code while running). + // - Recovery after running an exception handler or any error processing which may skip code after an error has been detected. + // - Error recovery is not perfect nor guaranteed! It is a feature to ease development. + // You not are not supposed to rely on it in the course of a normal application run. + // - Functions that support error recovery are using IM_ASSERT_USER_ERROR() instead of IM_ASSERT(). + // - By design, we do NOT allow error recovery to be 100% silent. One of the three options needs to be checked! + // - Always ensure that on programmers seats you have at minimum Asserts or Tooltips enabled when making direct imgui API calls! + // Otherwise it would severely hinder your ability to catch and correct mistakes! + // Read https://github.com/ocornut/imgui/wiki/Error-Handling for details. + // - Programmer seats: keep asserts (default), or disable asserts and keep error tooltips (new and nice!) + // - Non-programmer seats: maybe disable asserts, but make sure errors are resurfaced (tooltips, visible log entries, use callback etc.) + // - Recovery after error/exception: record stack sizes with ErrorRecoveryStoreState(), disable assert, set log callback (to e.g. trigger high-level breakpoint), recover with ErrorRecoveryTryToRecoverState(), restore settings. + bool ConfigErrorRecovery; // = true // Enable error recovery support. Some errors won't be detected and lead to direct crashes if recovery is disabled. + bool ConfigErrorRecoveryEnableAssert; // = true // Enable asserts on recoverable error. By default call IM_ASSERT() when returning from a failing IM_ASSERT_USER_ERROR() + bool ConfigErrorRecoveryEnableDebugLog; // = true // Enable debug log output on recoverable errors. + bool ConfigErrorRecoveryEnableTooltip; // = true // Enable tooltip on recoverable errors. The tooltip include a way to enable asserts if they were disabled. + // Option to enable various debug tools showing buttons that will call the IM_DEBUG_BREAK() macro. // - The Item Picker tool will be available regardless of this being enabled, in order to maximize its discoverability. // - Requires a debugger being attached, otherwise IM_DEBUG_BREAK() options will appear to crash your application. // e.g. io.ConfigDebugIsDebuggerPresent = ::IsDebuggerPresent() on Win32, or refer to ImOsIsDebuggerPresent() imgui_test_engine/imgui_te_utils.cpp for a Unix compatible version). bool ConfigDebugIsDebuggerPresent; // = false // Enable various tools calling IM_DEBUG_BREAK(). + // Tools to detect code submitting items with conflicting/duplicate IDs + // - Code should use PushID()/PopID() in loops, or append "##xx" to same-label identifiers. + // - Empty label e.g. Button("") == same ID as parent widget/node. Use Button("##xx") instead! + // - See FAQ https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#q-about-the-id-stack-system + bool ConfigDebugHighlightIdConflicts;// = true // Highlight and show an error message when multiple items have conflicting identifiers. + // Tools to test correct Begin/End and BeginChild/EndChild behaviors. // - Presently Begin()/End() and BeginChild()/EndChild() needs to ALWAYS be called in tandem, regardless of return value of BeginXXX() // - This is inconsistent with other BeginXXX functions and create confusion for many users. @@ -2191,16 +2396,17 @@ struct ImGuiIO // Option to deactivate io.AddFocusEvent(false) handling. // - May facilitate interactions with a debugger when focus loss leads to clearing inputs data. // - Backends may have other side-effects on focus loss, so this will reduce side-effects but not necessary remove all of them. - bool ConfigDebugIgnoreFocusLoss; // = false // Ignore io.AddFocusEvent(false), consequently not calling io.ClearInputKeys() in input processing. + bool ConfigDebugIgnoreFocusLoss; // = false // Ignore io.AddFocusEvent(false), consequently not calling io.ClearInputKeys()/io.ClearInputMouse() in input processing. - // Options to audit .ini data + // Option to audit .ini data bool ConfigDebugIniSettings; // = false // Save .ini data with extra comments (particularly helpful for Docking, but makes saving slower) //------------------------------------------------------------------ - // Platform Functions + // Platform Identifiers // (the imgui_impl_xxxx backend files are setting those up for you) //------------------------------------------------------------------ + // Nowadays those would be stored in ImGuiPlatformIO but we are leaving them here for legacy reasons. // Optional: Platform/Renderer backend name (informational only! will be displayed in About Window) + User data for backend/wrappers to store their own stuff. const char* BackendPlatformName; // = NULL const char* BackendRendererName; // = NULL @@ -2208,19 +2414,6 @@ struct ImGuiIO void* BackendRendererUserData; // = NULL // User data for renderer backend void* BackendLanguageUserData; // = NULL // User data for non C++ programming language backend - // Optional: Access OS clipboard - // (default to use native Win32 clipboard on Windows, otherwise uses a private clipboard. Override to access OS clipboard on other architectures) - const char* (*GetClipboardTextFn)(void* user_data); - void (*SetClipboardTextFn)(void* user_data, const char* text); - void* ClipboardUserData; - - // Optional: Notify OS Input Method Editor of the screen position of your cursor for text input position (e.g. when using Japanese/Chinese IME on Windows) - // (default to use native imm32 api on Windows) - void (*SetPlatformImeDataFn)(ImGuiViewport* viewport, ImGuiPlatformImeData* data); - - // Optional: Platform locale - ImWchar PlatformLocaleDecimalPoint; // '.' // [Experimental] Configure decimal point e.g. '.' or ',' useful for some languages (e.g. German), generally pulled from *localeconv()->decimal_point - //------------------------------------------------------------------ // Input - Call before calling NewFrame() //------------------------------------------------------------------ @@ -2232,6 +2425,7 @@ struct ImGuiIO IMGUI_API void AddMouseButtonEvent(int button, bool down); // Queue a mouse button change IMGUI_API void AddMouseWheelEvent(float wheel_x, float wheel_y); // Queue a mouse wheel update. wheel_y<0: scroll down, wheel_y>0: scroll up, wheel_x<0: scroll right, wheel_x>0: scroll left. IMGUI_API void AddMouseSourceEvent(ImGuiMouseSource source); // Queue a mouse source change (Mouse/TouchScreen/Pen) + IMGUI_API void AddMouseViewportEvent(ImGuiID id); // Queue a mouse hovered viewport. Requires backend to set ImGuiBackendFlags_HasMouseHoveredViewport to call this (for multi-viewport support). IMGUI_API void AddFocusEvent(bool focused); // Queue a gain/loss of focus for the application (generally based on OS/platform focus of your window) IMGUI_API void AddInputCharacter(unsigned int c); // Queue a new character input IMGUI_API void AddInputCharacterUTF16(ImWchar16 c); // Queue a new character input from a UTF-16 character, it can be a surrogate @@ -2240,7 +2434,8 @@ struct ImGuiIO IMGUI_API void SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native_scancode, int native_legacy_index = -1); // [Optional] Specify index for legacy <1.87 IsKeyXXX() functions with native indices + specify native keycode, scancode. IMGUI_API void SetAppAcceptingEvents(bool accepting_events); // Set master flag for accepting key/mouse/text events (default to true). Useful if you have native dialog boxes that are interrupting your application loop/refresh, and you want to disable events being queued while your app is frozen. IMGUI_API void ClearEventsQueue(); // Clear all incoming events. - IMGUI_API void ClearInputKeys(); // Clear current keyboard/mouse/gamepad state + current frame text input buffer. Equivalent to releasing all keys/buttons. + IMGUI_API void ClearInputKeys(); // Clear current keyboard/gamepad state + current frame text input buffer. Equivalent to releasing all keys/buttons. + IMGUI_API void ClearInputMouse(); // Clear current mouse state. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS IMGUI_API void ClearInputCharacters(); // [Obsoleted in 1.89.8] Clear the current frame text input buffer. Now included within ClearInputKeys(). #endif @@ -2254,10 +2449,10 @@ struct ImGuiIO bool WantCaptureMouse; // Set when Dear ImGui will use mouse inputs, in this case do not dispatch them to your main game/application (either way, always pass on mouse inputs to imgui). (e.g. unclicked mouse is hovering over an imgui window, widget is active, mouse was clicked over an imgui window, etc.). bool WantCaptureKeyboard; // Set when Dear ImGui will use keyboard inputs, in this case do not dispatch them to your main game/application (either way, always pass keyboard inputs to imgui). (e.g. InputText active, or an imgui window is focused and navigation is enabled, etc.). bool WantTextInput; // Mobile/console: when set, you may display an on-screen keyboard. This is set by Dear ImGui when it wants textual keyboard input to happen (e.g. when a InputText widget is active). - bool WantSetMousePos; // MousePos has been altered, backend should reposition mouse on next frame. Rarely used! Set only when ImGuiConfigFlags_NavEnableSetMousePos flag is enabled. + bool WantSetMousePos; // MousePos has been altered, backend should reposition mouse on next frame. Rarely used! Set only when io.ConfigNavMoveSetMousePos is enabled. bool WantSaveIniSettings; // When manual .ini load/save is active (io.IniFilename == NULL), this will be set to notify your application that you can call SaveIniSettingsToMemory() and save yourself. Important: clear io.WantSaveIniSettings yourself after saving! bool NavActive; // Keyboard/Gamepad navigation is currently allowed (will handle ImGuiKey_NavXXX events) = a window is focused and it doesn't use the ImGuiWindowFlags_NoNavInputs flag. - bool NavVisible; // Keyboard/Gamepad navigation is visible and allowed (will handle ImGuiKey_NavXXX events). + bool NavVisible; // Keyboard/Gamepad navigation highlight is visible and allowed (will handle ImGuiKey_NavXXX events). float Framerate; // Estimate of application framerate (rolling average over 60 frames, based on io.DeltaTime), in frame per second. Solely for convenience. Slow applications may not want to use a moving average or may want to reset underlying buffers occasionally. int MetricsRenderVertices; // Vertices output during last call to Render() int MetricsRenderIndices; // Indices output during last call to Render() = number of triangles * 3 @@ -2279,6 +2474,7 @@ struct ImGuiIO float MouseWheel; // Mouse wheel Vertical: 1 unit scrolls about 5 lines text. >0 scrolls Up, <0 scrolls Down. Hold SHIFT to turn vertical scroll into horizontal scroll. float MouseWheelH; // Mouse wheel Horizontal. >0 scrolls Left, <0 scrolls Right. Most users don't have a mouse with a horizontal wheel, may not be filled by all backends. ImGuiMouseSource MouseSource; // Mouse actual input peripheral (Mouse/TouchScreen/Pen). + ImGuiID MouseHoveredViewport; // (Optional) Modify using io.AddMouseViewportEvent(). With multi-viewports: viewport the OS mouse is hovering. If possible _IGNORING_ viewports with the ImGuiViewportFlags_NoInputs flag is much better (few backends can handle that). Set io.BackendFlags |= ImGuiBackendFlags_HasMouseHoveredViewport if you can provide this info. If you don't imgui will infer the value using the rectangles and last focused time of the viewports it knows about (ignoring other OS windows). bool KeyCtrl; // Keyboard modifier down: Control bool KeyShift; // Keyboard modifier down: Shift bool KeyAlt; // Keyboard modifier down: Alt @@ -2286,7 +2482,7 @@ struct ImGuiIO // Other state maintained from data above + IO function calls ImGuiKeyChord KeyMods; // Key mods flags (any of ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Alt/ImGuiMod_Super flags, same as io.KeyCtrl/KeyShift/KeyAlt/KeySuper but merged into flags. Read-only, updated by NewFrame() - ImGuiKeyData KeysData[ImGuiKey_KeysData_SIZE]; // Key state for all known keys. Use IsKeyXXX() functions to access this. + ImGuiKeyData KeysData[ImGuiKey_NamedKey_COUNT];// Key state for all known keys. Use IsKeyXXX() functions to access this. bool WantCaptureMouseUnlessPopupClose; // Alternative to WantCaptureMouse: (WantCaptureMouse == true && WantCaptureMouseUnlessPopupClose == false) when a click over void is expected to close a popup. ImVec2 MousePosPrev; // Previous mouse position (note that MouseDelta is not necessary == MousePos-MousePosPrev, in case either position is invalid) ImVec2 MouseClickedPos[5]; // Position at time of clicking @@ -2302,23 +2498,30 @@ struct ImGuiIO bool MouseCtrlLeftAsRightClick; // (OSX) Set to true when the current click was a ctrl-click that spawned a simulated right click float MouseDownDuration[5]; // Duration the mouse button has been down (0.0f == just clicked) float MouseDownDurationPrev[5]; // Previous time the mouse button has been down + ImVec2 MouseDragMaxDistanceAbs[5]; // Maximum distance, absolute, on each axis, of how much mouse has traveled from the clicking point float MouseDragMaxDistanceSqr[5]; // Squared maximum distance of how much mouse has traveled from the clicking point (used for moving thresholds) float PenPressure; // Touch/Pen pressure (0.0f to 1.0f, should be >0.0f only when MouseDown[0] == true). Helper storage currently unused by Dear ImGui. bool AppFocusLost; // Only modify via AddFocusEvent() bool AppAcceptingEvents; // Only modify via SetAppAcceptingEvents() - ImS8 BackendUsingLegacyKeyArrays; // -1: unknown, 0: using AddKeyEvent(), 1: using legacy io.KeysDown[] - bool BackendUsingLegacyNavInputArray; // 0: using AddKeyAnalogEvent(), 1: writing to legacy io.NavInputs[] directly ImWchar16 InputQueueSurrogate; // For AddInputCharacterUTF16() ImVector InputQueueCharacters; // Queue of _characters_ input (obtained by platform backend). Fill using AddInputCharacter() helper. // Legacy: before 1.87, we required backend to fill io.KeyMap[] (imgui->native map) during initialization and io.KeysDown[] (native indices) every frame. // This is still temporarily supported as a legacy feature. However the new preferred scheme is for backend to call io.AddKeyEvent(). // Old (<1.87): ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_Space]) --> New (1.87+) ImGui::IsKeyPressed(ImGuiKey_Space) -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - int KeyMap[ImGuiKey_COUNT]; // [LEGACY] Input: map of indices into the KeysDown[512] entries array which represent your "native" keyboard state. The first 512 are now unused and should be kept zero. Legacy backend will write into KeyMap[] using ImGuiKey_ indices which are always >512. - bool KeysDown[ImGuiKey_COUNT]; // [LEGACY] Input: Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). This used to be [512] sized. It is now ImGuiKey_COUNT to allow legacy io.KeysDown[GetKeyIndex(...)] to work without an overflow. - float NavInputs[ImGuiNavInput_COUNT]; // [LEGACY] Since 1.88, NavInputs[] was removed. Backends from 1.60 to 1.86 won't build. Feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums. + // Old (<1.87): ImGui::IsKeyPressed(MYPLATFORM_KEY_SPACE) --> New (1.87+) ImGui::IsKeyPressed(ImGuiKey_Space) + // Read https://github.com/ocornut/imgui/issues/4921 for details. + //int KeyMap[ImGuiKey_COUNT]; // [LEGACY] Input: map of indices into the KeysDown[512] entries array which represent your "native" keyboard state. The first 512 are now unused and should be kept zero. Legacy backend will write into KeyMap[] using ImGuiKey_ indices which are always >512. + //bool KeysDown[ImGuiKey_COUNT]; // [LEGACY] Input: Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). This used to be [512] sized. It is now ImGuiKey_COUNT to allow legacy io.KeysDown[GetKeyIndex(...)] to work without an overflow. + //float NavInputs[ImGuiNavInput_COUNT]; // [LEGACY] Since 1.88, NavInputs[] was removed. Backends from 1.60 to 1.86 won't build. Feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums. //void* ImeWindowHandle; // [Obsoleted in 1.87] Set ImGuiViewport::PlatformHandleRaw instead. Set this to your HWND to get automatic IME cursor positioning. + + // Legacy: before 1.91.1, clipboard functions were stored in ImGuiIO instead of ImGuiPlatformIO. + // As this is will affect all users of custom engines/backends, we are providing proper legacy redirection (will obsolete). +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + const char* (*GetClipboardTextFn)(void* user_data); + void (*SetClipboardTextFn)(void* user_data, const char* text); + void* ClipboardUserData; #endif IMGUI_API ImGuiIO(); @@ -2379,6 +2582,28 @@ struct ImGuiSizeCallbackData ImVec2 DesiredSize; // Read-write. Desired size, based on user's mouse position. Write to this field to restrain resizing. }; +// [ALPHA] Rarely used / very advanced uses only. Use with SetNextWindowClass() and DockSpace() functions. +// Important: the content of this class is still highly WIP and likely to change and be refactored +// before we stabilize Docking features. Please be mindful if using this. +// Provide hints: +// - To the platform backend via altered viewport flags (enable/disable OS decoration, OS task bar icons, etc.) +// - To the platform backend for OS level parent/child relationships of viewport. +// - To the docking system for various options and filtering. +struct ImGuiWindowClass +{ + ImGuiID ClassId; // User data. 0 = Default class (unclassed). Windows of different classes cannot be docked with each others. + ImGuiID ParentViewportId; // Hint for the platform backend. -1: use default. 0: request platform backend to not parent the platform. != 0: request platform backend to create a parent<>child relationship between the platform windows. Not conforming backends are free to e.g. parent every viewport to the main viewport or not. + ImGuiID FocusRouteParentWindowId; // ID of parent window for shortcut focus route evaluation, e.g. Shortcut() call from Parent Window will succeed when this window is focused. + ImGuiViewportFlags ViewportFlagsOverrideSet; // Viewport flags to set when a window of this class owns a viewport. This allows you to enforce OS decoration or task bar icon, override the defaults on a per-window basis. + ImGuiViewportFlags ViewportFlagsOverrideClear; // Viewport flags to clear when a window of this class owns a viewport. This allows you to enforce OS decoration or task bar icon, override the defaults on a per-window basis. + ImGuiTabItemFlags TabItemFlagsOverrideSet; // [EXPERIMENTAL] TabItem flags to set when a window of this class gets submitted into a dock node tab bar. May use with ImGuiTabItemFlags_Leading or ImGuiTabItemFlags_Trailing. + ImGuiDockNodeFlags DockNodeFlagsOverrideSet; // [EXPERIMENTAL] Dock node flags to set when a window of this class is hosted by a dock node (it doesn't have to be selected!) + bool DockingAlwaysTabBar; // Set to true to enforce single floating windows of this class always having their own docking node (equivalent of setting the global io.ConfigDockingAlwaysTabBar) + bool DockingAllowUnclassed; // Set to true to allow windows of this class to be docked/merged with an unclassed window. // FIXME-DOCK: Move to DockNodeFlags override? + + ImGuiWindowClass() { memset(this, 0, sizeof(*this)); ParentViewportId = (ImGuiID)-1; DockingAllowUnclassed = true; } +}; + // Data payload for Drag and Drop operations: AcceptDragDropPayload(), GetDragDropPayload() struct ImGuiPayload { @@ -2552,9 +2777,10 @@ struct ImGuiListClipper int ItemsCount; // [Internal] Number of items float ItemsHeight; // [Internal] Height of item after a first step and item submission can calculate it float StartPosY; // [Internal] Cursor position at the time of Begin() or after table frozen rows are all processed + double StartSeekOffsetY; // [Internal] Account for frozen rows in a table and initial loss of precision in very large windows. void* TempData; // [Internal] Internal data - // items_count: Use INT_MAX if you don't know how many items you have (in which case the cursor won't be advanced in the final step) + // items_count: Use INT_MAX if you don't know how many items you have (in which case the cursor won't be advanced in the final step, and you can call SeekCursorForItem() manually if you need) // items_height: Use -1.0f to be calculated automatically on first step. Otherwise pass in the distance between your items, typically GetTextLineHeightWithSpacing() or GetFrameHeightWithSpacing(). IMGUI_API ImGuiListClipper(); IMGUI_API ~ImGuiListClipper(); @@ -2567,6 +2793,11 @@ struct ImGuiListClipper inline void IncludeItemByIndex(int item_index) { IncludeItemsByIndex(item_index, item_index + 1); } IMGUI_API void IncludeItemsByIndex(int item_begin, int item_end); // item_end is exclusive e.g. use (42, 42+1) to make item 42 never clipped. + // Seek cursor toward given item. This is automatically called while stepping. + // - The only reason to call this is: you can use ImGuiListClipper::Begin(INT_MAX) if you don't know item count ahead of time. + // - In this case, after all steps are done, you'll want to call SeekCursorForItem(item_count). + IMGUI_API void SeekCursorForItem(int item_index); + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS inline void IncludeRangeByIndices(int item_begin, int item_end) { IncludeItemsByIndex(item_begin, item_end); } // [renamed in 1.89.9] inline void ForceDisplayRangeByIndices(int item_begin, int item_end) { IncludeItemsByIndex(item_begin, item_end); } // [renamed in 1.89.6] @@ -2577,7 +2808,7 @@ struct ImGuiListClipper // Helpers: ImVec2/ImVec4 operators // - It is important that we are keeping those disabled by default so they don't leak in user space. // - This is in order to allow user enabling implicit cast operators between ImVec2/ImVec4 and their own types (using IM_VEC2_CLASS_EXTRA in imconfig.h) -// - You can use '#define IMGUI_DEFINE_MATH_OPERATORS' to import our operators, provided as a courtesy. +// - Add '#define IMGUI_DEFINE_MATH_OPERATORS' before including this file (or in imconfig.h) to access courtesy maths operators for ImVec2 and ImVec4. #ifdef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED IM_MSVC_RUNTIME_CHECKS_OFF @@ -2605,7 +2836,8 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE #endif // Helpers macros to generate 32-bit encoded colors -// User can declare their own format by #defining the 5 _SHIFT/_MASK macros in their imconfig file. +// - User can declare their own format by #defining the 5 _SHIFT/_MASK macros in their imconfig file. +// - Any setting other than the default will need custom backend support. The only standard backend that supports anything else than the default is DirectX9. #ifndef IM_COL32_R_SHIFT #ifdef IMGUI_USE_BGRA_PACKED_COLOR #define IM_COL32_R_SHIFT 16 @@ -2647,6 +2879,154 @@ struct ImColor static ImColor HSV(float h, float s, float v, float a = 1.0f) { float r, g, b; ImGui::ColorConvertHSVtoRGB(h, s, v, r, g, b); return ImColor(r, g, b, a); } }; +//----------------------------------------------------------------------------- +// [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiSelectionRequestType, ImGuiSelectionRequest, ImGuiMultiSelectIO, ImGuiSelectionBasicStorage) +//----------------------------------------------------------------------------- + +// Multi-selection system +// Documentation at: https://github.com/ocornut/imgui/wiki/Multi-Select +// - Refer to 'Demo->Widgets->Selection State & Multi-Select' for demos using this. +// - This system implements standard multi-selection idioms (CTRL+Mouse/Keyboard, SHIFT+Mouse/Keyboard, etc) +// with support for clipper (skipping non-visible items), box-select and many other details. +// - Selectable(), Checkbox() are supported but custom widgets may use it as well. +// - TreeNode() is technically supported but... using this correctly is more complicated: you need some sort of linear/random access to your tree, +// which is suited to advanced trees setups also implementing filters and clipper. We will work toward simplifying and demoing it. +// - In the spirit of Dear ImGui design, your code owns actual selection data. +// This is designed to allow all kinds of selection storage you may use in your application e.g. set/map/hash. +// About ImGuiSelectionBasicStorage: +// - This is an optional helper to store a selection state and apply selection requests. +// - It is used by our demos and provided as a convenience to quickly implement multi-selection. +// Usage: +// - Identify submitted items with SetNextItemSelectionUserData(), most likely using an index into your current data-set. +// - Store and maintain actual selection data using persistent object identifiers. +// - Usage flow: +// BEGIN - (1) Call BeginMultiSelect() and retrieve the ImGuiMultiSelectIO* result. +// - (2) Honor request list (SetAll/SetRange requests) by updating your selection data. Same code as Step 6. +// - (3) [If using clipper] You need to make sure RangeSrcItem is always submitted. Calculate its index and pass to clipper.IncludeItemByIndex(). If storing indices in ImGuiSelectionUserData, a simple clipper.IncludeItemByIndex(ms_io->RangeSrcItem) call will work. +// LOOP - (4) Submit your items with SetNextItemSelectionUserData() + Selectable()/TreeNode() calls. +// END - (5) Call EndMultiSelect() and retrieve the ImGuiMultiSelectIO* result. +// - (6) Honor request list (SetAll/SetRange requests) by updating your selection data. Same code as Step 2. +// If you submit all items (no clipper), Step 2 and 3 are optional and will be handled by each item themselves. It is fine to always honor those steps. +// About ImGuiSelectionUserData: +// - This can store an application-defined identifier (e.g. index or pointer) submitted via SetNextItemSelectionUserData(). +// - In return we store them into RangeSrcItem/RangeFirstItem/RangeLastItem and other fields in ImGuiMultiSelectIO. +// - Most applications will store an object INDEX, hence the chosen name and type. Storing an index is natural, because +// SetRange requests will give you two end-points and you will need to iterate/interpolate between them to update your selection. +// - However it is perfectly possible to store a POINTER or another IDENTIFIER inside ImGuiSelectionUserData. +// Our system never assume that you identify items by indices, it never attempts to interpolate between two values. +// - If you enable ImGuiMultiSelectFlags_NoRangeSelect then it is guaranteed that you will never have to interpolate +// between two ImGuiSelectionUserData, which may be a convenient way to use part of the feature with less code work. +// - As most users will want to store an index, for convenience and to reduce confusion we use ImS64 instead of void*, +// being syntactically easier to downcast. Feel free to reinterpret_cast and store a pointer inside. + +// Flags for BeginMultiSelect() +enum ImGuiMultiSelectFlags_ +{ + ImGuiMultiSelectFlags_None = 0, + ImGuiMultiSelectFlags_SingleSelect = 1 << 0, // Disable selecting more than one item. This is available to allow single-selection code to share same code/logic if desired. It essentially disables the main purpose of BeginMultiSelect() tho! + ImGuiMultiSelectFlags_NoSelectAll = 1 << 1, // Disable CTRL+A shortcut to select all. + ImGuiMultiSelectFlags_NoRangeSelect = 1 << 2, // Disable Shift+selection mouse/keyboard support (useful for unordered 2D selection). With BoxSelect is also ensure contiguous SetRange requests are not combined into one. This allows not handling interpolation in SetRange requests. + ImGuiMultiSelectFlags_NoAutoSelect = 1 << 3, // Disable selecting items when navigating (useful for e.g. supporting range-select in a list of checkboxes). + ImGuiMultiSelectFlags_NoAutoClear = 1 << 4, // Disable clearing selection when navigating or selecting another one (generally used with ImGuiMultiSelectFlags_NoAutoSelect. useful for e.g. supporting range-select in a list of checkboxes). + ImGuiMultiSelectFlags_NoAutoClearOnReselect = 1 << 5, // Disable clearing selection when clicking/selecting an already selected item. + ImGuiMultiSelectFlags_BoxSelect1d = 1 << 6, // Enable box-selection with same width and same x pos items (e.g. full row Selectable()). Box-selection works better with little bit of spacing between items hit-box in order to be able to aim at empty space. + ImGuiMultiSelectFlags_BoxSelect2d = 1 << 7, // Enable box-selection with varying width or varying x pos items support (e.g. different width labels, or 2D layout/grid). This is slower: alters clipping logic so that e.g. horizontal movements will update selection of normally clipped items. + ImGuiMultiSelectFlags_BoxSelectNoScroll = 1 << 8, // Disable scrolling when box-selecting near edges of scope. + ImGuiMultiSelectFlags_ClearOnEscape = 1 << 9, // Clear selection when pressing Escape while scope is focused. + ImGuiMultiSelectFlags_ClearOnClickVoid = 1 << 10, // Clear selection when clicking on empty location within scope. + ImGuiMultiSelectFlags_ScopeWindow = 1 << 11, // Scope for _BoxSelect and _ClearOnClickVoid is whole window (Default). Use if BeginMultiSelect() covers a whole window or used a single time in same window. + ImGuiMultiSelectFlags_ScopeRect = 1 << 12, // Scope for _BoxSelect and _ClearOnClickVoid is rectangle encompassing BeginMultiSelect()/EndMultiSelect(). Use if BeginMultiSelect() is called multiple times in same window. + ImGuiMultiSelectFlags_SelectOnClick = 1 << 13, // Apply selection on mouse down when clicking on unselected item. (Default) + ImGuiMultiSelectFlags_SelectOnClickRelease = 1 << 14, // Apply selection on mouse release when clicking an unselected item. Allow dragging an unselected item without altering selection. + //ImGuiMultiSelectFlags_RangeSelect2d = 1 << 15, // Shift+Selection uses 2d geometry instead of linear sequence, so possible to use Shift+up/down to select vertically in grid. Analogous to what BoxSelect does. + ImGuiMultiSelectFlags_NavWrapX = 1 << 16, // [Temporary] Enable navigation wrapping on X axis. Provided as a convenience because we don't have a design for the general Nav API for this yet. When the more general feature be public we may obsolete this flag in favor of new one. +}; + +// Main IO structure returned by BeginMultiSelect()/EndMultiSelect(). +// This mainly contains a list of selection requests. +// - Use 'Demo->Tools->Debug Log->Selection' to see requests as they happen. +// - Some fields are only useful if your list is dynamic and allows deletion (getting post-deletion focus/state right is shown in the demo) +// - Below: who reads/writes each fields? 'r'=read, 'w'=write, 'ms'=multi-select code, 'app'=application/user code. +struct ImGuiMultiSelectIO +{ + //------------------------------------------// BeginMultiSelect / EndMultiSelect + ImVector Requests; // ms:w, app:r / ms:w app:r // Requests to apply to your selection data. + ImGuiSelectionUserData RangeSrcItem; // ms:w app:r / // (If using clipper) Begin: Source item (often the first selected item) must never be clipped: use clipper.IncludeItemByIndex() to ensure it is submitted. + ImGuiSelectionUserData NavIdItem; // ms:w, app:r / // (If using deletion) Last known SetNextItemSelectionUserData() value for NavId (if part of submitted items). + bool NavIdSelected; // ms:w, app:r / app:r // (If using deletion) Last known selection state for NavId (if part of submitted items). + bool RangeSrcReset; // app:w / ms:r // (If using deletion) Set before EndMultiSelect() to reset ResetSrcItem (e.g. if deleted selection). + int ItemsCount; // ms:w, app:r / app:r // 'int items_count' parameter to BeginMultiSelect() is copied here for convenience, allowing simpler calls to your ApplyRequests handler. Not used internally. +}; + +// Selection request type +enum ImGuiSelectionRequestType +{ + ImGuiSelectionRequestType_None = 0, + ImGuiSelectionRequestType_SetAll, // Request app to clear selection (if Selected==false) or select all items (if Selected==true). We cannot set RangeFirstItem/RangeLastItem as its contents is entirely up to user (not necessarily an index) + ImGuiSelectionRequestType_SetRange, // Request app to select/unselect [RangeFirstItem..RangeLastItem] items (inclusive) based on value of Selected. Only EndMultiSelect() request this, app code can read after BeginMultiSelect() and it will always be false. +}; + +// Selection request item +struct ImGuiSelectionRequest +{ + //------------------------------------------// BeginMultiSelect / EndMultiSelect + ImGuiSelectionRequestType Type; // ms:w, app:r / ms:w, app:r // Request type. You'll most often receive 1 Clear + 1 SetRange with a single-item range. + bool Selected; // ms:w, app:r / ms:w, app:r // Parameter for SetAll/SetRange requests (true = select, false = unselect) + ImS8 RangeDirection; // / ms:w app:r // Parameter for SetRange request: +1 when RangeFirstItem comes before RangeLastItem, -1 otherwise. Useful if you want to preserve selection order on a backward Shift+Click. + ImGuiSelectionUserData RangeFirstItem; // / ms:w, app:r // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from top to bottom). + ImGuiSelectionUserData RangeLastItem; // / ms:w, app:r // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from bottom to top). Inclusive! +}; + +// Optional helper to store multi-selection state + apply multi-selection requests. +// - Used by our demos and provided as a convenience to easily implement basic multi-selection. +// - Iterate selection with 'void* it = NULL; ImGuiID id; while (selection.GetNextSelectedItem(&it, &id)) { ... }' +// Or you can check 'if (Contains(id)) { ... }' for each possible object if their number is not too high to iterate. +// - USING THIS IS NOT MANDATORY. This is only a helper and not a required API. +// To store a multi-selection, in your application you could: +// - Use this helper as a convenience. We use our simple key->value ImGuiStorage as a std::set replacement. +// - Use your own external storage: e.g. std::set, std::vector, interval trees, intrusively stored selection etc. +// In ImGuiSelectionBasicStorage we: +// - always use indices in the multi-selection API (passed to SetNextItemSelectionUserData(), retrieved in ImGuiMultiSelectIO) +// - use the AdapterIndexToStorageId() indirection layer to abstract how persistent selection data is derived from an index. +// - use decently optimized logic to allow queries and insertion of very large selection sets. +// - do not preserve selection order. +// Many combinations are possible depending on how you prefer to store your items and how you prefer to store your selection. +// Large applications are likely to eventually want to get rid of this indirection layer and do their own thing. +// See https://github.com/ocornut/imgui/wiki/Multi-Select for details and pseudo-code using this helper. +struct ImGuiSelectionBasicStorage +{ + // Members + int Size; // // Number of selected items, maintained by this helper. + bool PreserveOrder; // = false // GetNextSelectedItem() will return ordered selection (currently implemented by two additional sorts of selection. Could be improved) + void* UserData; // = NULL // User data for use by adapter function // e.g. selection.UserData = (void*)my_items; + ImGuiID (*AdapterIndexToStorageId)(ImGuiSelectionBasicStorage* self, int idx); // e.g. selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { return ((MyItems**)self->UserData)[idx]->ID; }; + int _SelectionOrder;// [Internal] Increasing counter to store selection order + ImGuiStorage _Storage; // [Internal] Selection set. Think of this as similar to e.g. std::set. Prefer not accessing directly: iterate with GetNextSelectedItem(). + + // Methods + IMGUI_API ImGuiSelectionBasicStorage(); + IMGUI_API void ApplyRequests(ImGuiMultiSelectIO* ms_io); // Apply selection requests coming from BeginMultiSelect() and EndMultiSelect() functions. It uses 'items_count' passed to BeginMultiSelect() + IMGUI_API bool Contains(ImGuiID id) const; // Query if an item id is in selection. + IMGUI_API void Clear(); // Clear selection + IMGUI_API void Swap(ImGuiSelectionBasicStorage& r); // Swap two selections + IMGUI_API void SetItemSelected(ImGuiID id, bool selected); // Add/remove an item from selection (generally done by ApplyRequests() function) + IMGUI_API bool GetNextSelectedItem(void** opaque_it, ImGuiID* out_id); // Iterate selection with 'void* it = NULL; ImGuiId id; while (selection.GetNextSelectedItem(&it, &id)) { ... }' + inline ImGuiID GetStorageIdFromIndex(int idx) { return AdapterIndexToStorageId(this, idx); } // Convert index to item id based on provided adapter. +}; + +// Optional helper to apply multi-selection requests to existing randomly accessible storage. +// Convenient if you want to quickly wire multi-select API on e.g. an array of bool or items storing their own selection state. +struct ImGuiSelectionExternalStorage +{ + // Members + void* UserData; // User data for use by adapter function // e.g. selection.UserData = (void*)my_items; + void (*AdapterSetItemSelected)(ImGuiSelectionExternalStorage* self, int idx, bool selected); // e.g. AdapterSetItemSelected = [](ImGuiSelectionExternalStorage* self, int idx, bool selected) { ((MyItems**)self->UserData)[idx]->Selected = selected; } + + // Methods + IMGUI_API ImGuiSelectionExternalStorage(); + IMGUI_API void ApplyRequests(ImGuiMultiSelectIO* ms_io); // Apply selection requests by using AdapterSetItemSelected() calls +}; + //----------------------------------------------------------------------------- // [SECTION] Drawing API (ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawListFlags, ImDrawList, ImDrawData) // Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of ImDrawList. @@ -2687,9 +3067,11 @@ struct ImDrawCmd unsigned int IdxOffset; // 4 // Start offset in index buffer. unsigned int ElemCount; // 4 // Number of indices (multiple of 3) to be rendered as triangles. Vertices are stored in the callee ImDrawList's vtx_buffer[] array, indices in idx_buffer[]. ImDrawCallback UserCallback; // 4-8 // If != NULL, call the function instead of rendering the vertices. clip_rect and texture_id will be set normally. - void* UserCallbackData; // 4-8 // The draw callback code can access this. + void* UserCallbackData; // 4-8 // Callback user data (when UserCallback != NULL). If called AddCallback() with size == 0, this is a copy of the AddCallback() argument. If called AddCallback() with size > 0, this is pointing to a buffer where data is stored. + int UserCallbackDataSize; // 4 // Size of callback user data when using storage, otherwise 0. + int UserCallbackDataOffset;// 4 // [Internal] Offset of callback user data when using storage, otherwise -1. - ImDrawCmd() { memset(this, 0, sizeof(*this)); } // Also ensure our padding fields are zeroed + ImDrawCmd() { memset(this, 0, sizeof(*this)); } // Also ensure our padding fields are zeroed // Since 1.83: returns ImTextureID associated with this draw call. Warning: DO NOT assume this is always same as 'TextureId' (we will change this function for an upcoming feature) inline ImTextureID GetTexID() const { return TextureId; } @@ -2782,7 +3164,7 @@ enum ImDrawListFlags_ // access the current window draw list and draw custom primitives. // You can interleave normal ImGui:: calls and adding primitives to the current draw list. // In single viewport mode, top-left is == GetMainViewport()->Pos (generally 0,0), bottom-right is == GetMainViewport()->Pos+Size (generally io.DisplaySize). -// You are totally free to apply whatever transformation matrix to want to the data (depending on the use of the transformation you may want to apply it to ClipRect as well!) +// You are totally free to apply whatever transformation matrix you want to the data (depending on the use of the transformation you may want to apply it to ClipRect as well!) // Important: Primitives are always added to the list and not culled (culling is done at higher-level by ImGui:: functions), if you use this API a lot consider coarse culling your drawn objects. struct ImDrawList { @@ -2802,13 +3184,15 @@ struct ImDrawList ImDrawListSplitter _Splitter; // [Internal] for channels api (note: prefer using your own persistent instance of ImDrawListSplitter!) ImVector _ClipRectStack; // [Internal] ImVector _TextureIdStack; // [Internal] + ImVector _CallbacksDataBuf; // [Internal] float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content const char* _OwnerName; // Pointer to owner window's name for debugging - // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData() or create and use your own ImDrawListSharedData (so you can use ImDrawList without ImGui) - ImDrawList(ImDrawListSharedData* shared_data) { memset(this, 0, sizeof(*this)); _Data = shared_data; } + // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData(). + // (advanced: you may create and use your own ImDrawListSharedData so you can use ImDrawList without ImGui, but that's more involved) + IMGUI_API ImDrawList(ImDrawListSharedData* shared_data); + IMGUI_API ~ImDrawList(); - ~ImDrawList() { _ClearFreeMemory(); } IMGUI_API void PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect = false); // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) IMGUI_API void PushClipRectFullScreen(); IMGUI_API void PopClipRect(); @@ -2839,7 +3223,7 @@ struct ImDrawList IMGUI_API void AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f); IMGUI_API void AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0); IMGUI_API void AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL); - IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); + IMGUI_API void AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); IMGUI_API void AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0); // Cubic Bezier (4 control points) IMGUI_API void AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments = 0); // Quadratic Bezier (3 control points) @@ -2874,8 +3258,18 @@ struct ImDrawList IMGUI_API void PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, int num_segments = 0); // Quadratic Bezier (3 control points) IMGUI_API void PathRect(const ImVec2& rect_min, const ImVec2& rect_max, float rounding = 0.0f, ImDrawFlags flags = 0); - // Advanced - IMGUI_API void AddCallback(ImDrawCallback callback, void* callback_data); // Your rendering function must check for 'UserCallback' in ImDrawCmd and call the function instead of rendering triangles. + // Advanced: Draw Callbacks + // - May be used to alter render state (change sampler, blending, current shader). May be used to emit custom rendering commands (difficult to do correctly, but possible). + // - Use special ImDrawCallback_ResetRenderState callback to instruct backend to reset its render state to the default. + // - Your rendering loop must check for 'UserCallback' in ImDrawCmd and call the function instead of rendering triangles. All standard backends are honoring this. + // - For some backends, the callback may access selected render-states exposed by the backend in a ImGui_ImplXXXX_RenderState structure pointed to by platform_io.Renderer_RenderState. + // - IMPORTANT: please be mindful of the different level of indirection between using size==0 (copying argument) and using size>0 (copying pointed data into a buffer). + // - If userdata_size == 0: we copy/store the 'userdata' argument as-is. It will be available unmodified in ImDrawCmd::UserCallbackData during render. + // - If userdata_size > 0, we copy/store 'userdata_size' bytes pointed to by 'userdata'. We store them in a buffer stored inside the drawlist. ImDrawCmd::UserCallbackData will point inside that buffer so you have to retrieve data from there. Your callback may need to use ImDrawCmd::UserCallbackDataSize if you expect dynamically-sized data. + // - Support for userdata_size > 0 was added in v1.91.4, October 2024. So earlier code always only allowed to copy/store a simple void*. + IMGUI_API void AddCallback(ImDrawCallback callback, void* userdata, size_t userdata_size = 0); + + // Advanced: Miscellaneous IMGUI_API void AddDrawCmd(); // This is useful if you need to forcefully create a new draw call (to allow for dependent rendering / blending). Otherwise primitives are merged into the same draw-call as much as possible IMGUI_API ImDrawList* CloneOutput() const; // Create a clone of the CmdBuffer/IdxBuffer/VtxBuffer. @@ -2905,8 +3299,8 @@ struct ImDrawList //inline void AddEllipse(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f) { AddEllipse(center, ImVec2(radius_x, radius_y), col, rot, num_segments, thickness); } // OBSOLETED in 1.90.5 (Mar 2024) //inline void AddEllipseFilled(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0) { AddEllipseFilled(center, ImVec2(radius_x, radius_y), col, rot, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024) //inline void PathEllipticalArcTo(const ImVec2& center, float radius_x, float radius_y, float rot, float a_min, float a_max, int num_segments = 0) { PathEllipticalArcTo(center, ImVec2(radius_x, radius_y), rot, a_min, a_max, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024) - //inline void AddBezierCurve(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0) { AddBezierCubic(p1, p2, p3, p4, col, thickness, num_segments); } // OBSOLETED in 1.80 (Jan 2021) - //inline void PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0) { PathBezierCubicCurveTo(p2, p3, p4, num_segments); } // OBSOLETED in 1.80 (Jan 2021) + //inline void AddBezierCurve(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0) { AddBezierCubic(p1, p2, p3, p4, col, thickness, num_segments); } // OBSOLETED in 1.80 (Jan 2021) + //inline void PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0) { PathBezierCubicCurveTo(p2, p3, p4, num_segments); } // OBSOLETED in 1.80 (Jan 2021) // [Internal helpers] IMGUI_API void _ResetForNewFrame(); @@ -2916,6 +3310,7 @@ struct ImDrawList IMGUI_API void _OnChangedClipRect(); IMGUI_API void _OnChangedTextureID(); IMGUI_API void _OnChangedVtxOffset(); + IMGUI_API void _SetTextureID(ImTextureID texture_id); IMGUI_API int _CalcCircleAutoSegmentCount(float radius) const; IMGUI_API void _PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step); IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); @@ -2927,7 +3322,7 @@ struct ImDrawList struct ImDrawData { bool Valid; // Only valid after Render() is called and before the next NewFrame() is called. - int CmdListsCount; // Number of ImDrawList* to render (should always be == CmdLists.size) + int CmdListsCount; // Number of ImDrawList* to render int TotalIdxCount; // For convenience, sum of all ImDrawList's IdxBuffer.Size int TotalVtxCount; // For convenience, sum of all ImDrawList's VtxBuffer.Size ImVector CmdLists; // Array of ImDrawList* to render. The ImDrawLists are owned by ImGuiContext and only pointed to from here. @@ -2957,8 +3352,8 @@ struct ImFontConfig float SizePixels; // // Size in pixels for rasterizer (more or less maps to the resulting font height). int OversampleH; // 2 // Rasterize at higher quality for sub-pixel positioning. Note the difference between 2 and 3 is minimal. You can reduce this to 1 for large glyphs save memory. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details. int OversampleV; // 1 // Rasterize at higher quality for sub-pixel positioning. This is not really useful as we don't use sub-pixel positions on the Y axis. - bool PixelSnapH; // false // Align every glyph to pixel boundary. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. - ImVec2 GlyphExtraSpacing; // 0, 0 // Extra spacing (in pixels) between glyphs. Only X axis is supported for now. + bool PixelSnapH; // false // Align every glyph AdvanceX to pixel boundaries. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. + ImVec2 GlyphExtraSpacing; // 0, 0 // Extra spacing (in pixels) between glyphs when rendered: essentially add to glyph->AdvanceX. Only X axis is supported for now. ImVec2 GlyphOffset; // 0, 0 // Offset all glyphs from this font input. const ImWchar* GlyphRanges; // NULL // THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font @@ -3007,13 +3402,16 @@ struct ImFontGlyphRangesBuilder // See ImFontAtlas::AddCustomRectXXX functions. struct ImFontAtlasCustomRect { - unsigned short Width, Height; // Input // Desired rectangle dimension unsigned short X, Y; // Output // Packed position in Atlas - unsigned int GlyphID; // Input // For custom font glyphs only (ID < 0x110000) + + // [Internal] + unsigned short Width, Height; // Input // Desired rectangle dimension + unsigned int GlyphID : 31; // Input // For custom font glyphs only (ID < 0x110000) + unsigned int GlyphColored : 1; // Input // For custom font glyphs only: glyph is colored, removed tinting. float GlyphAdvanceX; // Input // For custom font glyphs only: glyph xadvance ImVec2 GlyphOffset; // Input // For custom font glyphs only: glyph display offset ImFont* Font; // Input // For custom font glyphs only: target font - ImFontAtlasCustomRect() { Width = Height = 0; X = Y = 0xFFFF; GlyphID = 0; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0, 0); Font = NULL; } + ImFontAtlasCustomRect() { X = Y = 0xFFFF; Width = Height = 0; GlyphID = 0; GlyphColored = 0; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0, 0); Font = NULL; } bool IsPacked() const { return X != 0xFFFF; } }; @@ -3113,7 +3511,7 @@ struct ImFontAtlas ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) ImTextureID TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. - int TexGlyphPadding; // Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0 (will also need to set AntiAliasedLinesUseTex = false). + int TexGlyphPadding; // FIXME: Should be called "TexPackPadding". Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0 (will also need to set AntiAliasedLinesUseTex = false). bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. void* UserData; // Store your own atlas related user-data (if e.g. you have multiple font atlas). @@ -3149,46 +3547,47 @@ struct ImFontAtlas // ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or GetTexDataAsRGBA32(). struct ImFont { - // Members: Hot ~20/24 bytes (for CalcTextSize) - ImVector IndexAdvanceX; // 12-16 // out // // Sparse. Glyphs->AdvanceX in a directly indexable way (cache-friendly for CalcTextSize functions which only this this info, and are often bottleneck in large UI). + // [Internal] Members: Hot ~20/24 bytes (for CalcTextSize) + ImVector IndexAdvanceX; // 12-16 // out // // Sparse. Glyphs->AdvanceX in a directly indexable way (cache-friendly for CalcTextSize functions which only this info, and are often bottleneck in large UI). float FallbackAdvanceX; // 4 // out // = FallbackGlyph->AdvanceX float FontSize; // 4 // in // // Height of characters/line, set during loading (don't change after loading) - // Members: Hot ~28/40 bytes (for CalcTextSize + render loop) + // [Internal] Members: Hot ~28/40 bytes (for RenderText loop) ImVector IndexLookup; // 12-16 // out // // Sparse. Index glyphs by Unicode code-point. ImVector Glyphs; // 12-16 // out // // All glyphs. const ImFontGlyph* FallbackGlyph; // 4-8 // out // = FindGlyph(FontFallbackChar) - // Members: Cold ~32/40 bytes + // [Internal] Members: Cold ~32/40 bytes + // Conceptually ConfigData[] is the list of font sources merged to create this font. ImFontAtlas* ContainerAtlas; // 4-8 // out // // What we has been loaded into - const ImFontConfig* ConfigData; // 4-8 // in // // Pointer within ContainerAtlas->ConfigData + const ImFontConfig* ConfigData; // 4-8 // in // // Pointer within ContainerAtlas->ConfigData to ConfigDataCount instances short ConfigDataCount; // 2 // in // ~ 1 // Number of ImFontConfig involved in creating this font. Bigger than 1 when merging multiple font sources into one ImFont. - ImWchar FallbackChar; // 2 // out // = FFFD/'?' // Character used if a glyph isn't found. - ImWchar EllipsisChar; // 2 // out // = '...'/'.'// Character used for ellipsis rendering. short EllipsisCharCount; // 1 // out // 1 or 3 + ImWchar EllipsisChar; // 2-4 // out // = '...'/'.'// Character used for ellipsis rendering. + ImWchar FallbackChar; // 2-4 // out // = FFFD/'?' // Character used if a glyph isn't found. float EllipsisWidth; // 4 // out // Width float EllipsisCharStep; // 4 // out // Step between characters when EllipsisCount > 0 bool DirtyLookupTables; // 1 // out // float Scale; // 4 // in // = 1.f // Base font scale, multiplied by the per-window font scale which you can adjust with SetWindowFontScale() - float Ascent, Descent; // 4+4 // out // // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] + float Ascent, Descent; // 4+4 // out // // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] (unscaled) int MetricsTotalSurface;// 4 // out // // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) ImU8 Used4kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/4096/8]; // 2 bytes if ImWchar=ImWchar16, 34 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. // Methods IMGUI_API ImFont(); IMGUI_API ~ImFont(); - IMGUI_API const ImFontGlyph*FindGlyph(ImWchar c) const; - IMGUI_API const ImFontGlyph*FindGlyphNoFallback(ImWchar c) const; - float GetCharAdvance(ImWchar c) const { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; } + IMGUI_API const ImFontGlyph*FindGlyph(ImWchar c); + IMGUI_API const ImFontGlyph*FindGlyphNoFallback(ImWchar c); + float GetCharAdvance(ImWchar c) { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; } bool IsLoaded() const { return ContainerAtlas != NULL; } const char* GetDebugName() const { return ConfigData ? ConfigData->Name : ""; } // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. - IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 - IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; - IMGUI_API void RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c) const; - IMGUI_API void RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false) const; + IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL); // utf8 + IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width); + IMGUI_API void RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c); + IMGUI_API void RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false); // [Internal] Don't use! IMGUI_API void BuildLookupTable(); @@ -3210,11 +3609,24 @@ enum ImGuiViewportFlags_ ImGuiViewportFlags_None = 0, ImGuiViewportFlags_IsPlatformWindow = 1 << 0, // Represent a Platform Window ImGuiViewportFlags_IsPlatformMonitor = 1 << 1, // Represent a Platform Monitor (unused yet) - ImGuiViewportFlags_OwnedByApp = 1 << 2, // Platform Window: is created/managed by the application (rather than a dear imgui backend) + ImGuiViewportFlags_OwnedByApp = 1 << 2, // Platform Window: Is created/managed by the user application? (rather than our backend) + ImGuiViewportFlags_NoDecoration = 1 << 3, // Platform Window: Disable platform decorations: title bar, borders, etc. (generally set all windows, but if ImGuiConfigFlags_ViewportsDecoration is set we only set this on popups/tooltips) + ImGuiViewportFlags_NoTaskBarIcon = 1 << 4, // Platform Window: Disable platform task bar icon (generally set on popups/tooltips, or all windows if ImGuiConfigFlags_ViewportsNoTaskBarIcon is set) + ImGuiViewportFlags_NoFocusOnAppearing = 1 << 5, // Platform Window: Don't take focus when created. + ImGuiViewportFlags_NoFocusOnClick = 1 << 6, // Platform Window: Don't take focus when clicked on. + ImGuiViewportFlags_NoInputs = 1 << 7, // Platform Window: Make mouse pass through so we can drag this window while peaking behind it. + ImGuiViewportFlags_NoRendererClear = 1 << 8, // Platform Window: Renderer doesn't need to clear the framebuffer ahead (because we will fill it entirely). + ImGuiViewportFlags_NoAutoMerge = 1 << 9, // Platform Window: Avoid merging this window into another host window. This can only be set via ImGuiWindowClass viewport flags override (because we need to now ahead if we are going to create a viewport in the first place!). + ImGuiViewportFlags_TopMost = 1 << 10, // Platform Window: Display on top (for tooltips only). + ImGuiViewportFlags_CanHostOtherWindows = 1 << 11, // Viewport can host multiple imgui windows (secondary viewports are associated to a single window). // FIXME: In practice there's still probably code making the assumption that this is always and only on the MainViewport. Will fix once we add support for "no main viewport". + + // Output status flags (from Platform) + ImGuiViewportFlags_IsMinimized = 1 << 12, // Platform Window: Window is minimized, can skip render. When minimized we tend to avoid using the viewport pos/size for clipping window or testing if they are contained in the viewport. + ImGuiViewportFlags_IsFocused = 1 << 13, // Platform Window: Window is focused (last call to Platform_GetWindowFocus() returned true) }; // - Currently represents the Platform Window created by the application which is hosting our Dear ImGui windows. -// - In 'docking' branch with multi-viewport enabled, we extend this concept to have multiple active viewports. +// - With multi-viewport enabled, we extend this concept to have multiple active viewports. // - In the future we will extend this concept further to also represent Platform Monitor and support a "no main platform window" operation mode. // - About Main Area vs Work Area: // - Main Area = entire viewport. @@ -3228,11 +3640,26 @@ struct ImGuiViewport ImVec2 Size; // Main Area: Size of the viewport. ImVec2 WorkPos; // Work Area: Position of the viewport minus task bars, menus bars, status bars (>= Pos) ImVec2 WorkSize; // Work Area: Size of the viewport minus task bars, menu bars, status bars (<= Size) + float DpiScale; // 1.0f = 96 DPI = No extra scale. + ImGuiID ParentViewportId; // (Advanced) 0: no parent. Instruct the platform backend to setup a parent/child relationship between platform windows. + ImDrawData* DrawData; // The ImDrawData corresponding to this viewport. Valid after Render() and until the next call to NewFrame(). // Platform/Backend Dependent Data - void* PlatformHandleRaw; // void* to hold lower-level, platform-native window handle (under Win32 this is expected to be a HWND, unused for other platforms) + // Our design separate the Renderer and Platform backends to facilitate combining default backends with each others. + // When our create your own backend for a custom engine, it is possible that both Renderer and Platform will be handled + // by the same system and you may not need to use all the UserData/Handle fields. + // The library never uses those fields, they are merely storage to facilitate backend implementation. + void* RendererUserData; // void* to hold custom data structure for the renderer (e.g. swap chain, framebuffers etc.). generally set by your Renderer_CreateWindow function. + void* PlatformUserData; // void* to hold custom data structure for the OS / platform (e.g. windowing info, render context). generally set by your Platform_CreateWindow function. + void* PlatformHandle; // void* to hold higher-level, platform window handle (e.g. HWND, GLFWWindow*, SDL_Window*), for FindViewportByPlatformHandle(). + void* PlatformHandleRaw; // void* to hold lower-level, platform-native window handle (under Win32 this is expected to be a HWND, unused for other platforms), when using an abstraction layer like GLFW or SDL (where PlatformHandle would be a SDL_Window*) + bool PlatformWindowCreated; // Platform window has been created (Platform_CreateWindow() has been called). This is false during the first frame where a viewport is being created. + bool PlatformRequestMove; // Platform window requested move (e.g. window was moved by the OS / host window manager, authoritative position will be OS window position) + bool PlatformRequestResize; // Platform window requested resize (e.g. window was resized by the OS / host window manager, authoritative size will be OS window size) + bool PlatformRequestClose; // Platform window requested closure (e.g. window was moved by the OS / host window manager, e.g. pressing ALT-F4) ImGuiViewport() { memset(this, 0, sizeof(*this)); } + ~ImGuiViewport() { IM_ASSERT(PlatformUserData == NULL && RendererUserData == NULL); } // Helpers ImVec2 GetCenter() const { return ImVec2(Pos.x + Size.x * 0.5f, Pos.y + Size.y * 0.5f); } @@ -3240,10 +3667,162 @@ struct ImGuiViewport }; //----------------------------------------------------------------------------- -// [SECTION] Platform Dependent Interfaces +// [SECTION] ImGuiPlatformIO + other Platform Dependent Interfaces (ImGuiPlatformMonitor, ImGuiPlatformImeData) //----------------------------------------------------------------------------- -// (Optional) Support for IME (Input Method Editor) via the io.SetPlatformImeDataFn() function. +// [BETA] (Optional) Multi-Viewport Support! +// If you are new to Dear ImGui and trying to integrate it into your engine, you can probably ignore this for now. +// +// This feature allows you to seamlessly drag Dear ImGui windows outside of your application viewport. +// This is achieved by creating new Platform/OS windows on the fly, and rendering into them. +// Dear ImGui manages the viewport structures, and the backend create and maintain one Platform/OS window for each of those viewports. +// +// See Recap: https://github.com/ocornut/imgui/wiki/Multi-Viewports +// See Glossary https://github.com/ocornut/imgui/wiki/Glossary for details about some of the terminology. +// +// About the coordinates system: +// - When multi-viewports are enabled, all Dear ImGui coordinates become absolute coordinates (same as OS coordinates!) +// - So e.g. ImGui::SetNextWindowPos(ImVec2(0,0)) will position a window relative to your primary monitor! +// - If you want to position windows relative to your main application viewport, use ImGui::GetMainViewport()->Pos as a base position. +// +// Steps to use multi-viewports in your application, when using a default backend from the examples/ folder: +// - Application: Enable feature with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. +// - Backend: The backend initialization will setup all necessary ImGuiPlatformIO's functions and update monitors info every frame. +// - Application: In your main loop, call ImGui::UpdatePlatformWindows(), ImGui::RenderPlatformWindowsDefault() after EndFrame() or Render(). +// - Application: Fix absolute coordinates used in ImGui::SetWindowPos() or ImGui::SetNextWindowPos() calls. +// +// Steps to use multi-viewports in your application, when using a custom backend: +// - Important: THIS IS NOT EASY TO DO and comes with many subtleties not described here! +// It's also an experimental feature, so some of the requirements may evolve. +// Consider using default backends if you can. Either way, carefully follow and refer to examples/ backends for details. +// - Application: Enable feature with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. +// - Backend: Hook ImGuiPlatformIO's Platform_* and Renderer_* callbacks (see below). +// Set 'io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports' and 'io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports'. +// Update ImGuiPlatformIO's Monitors list every frame. +// Update MousePos every frame, in absolute coordinates. +// - Application: In your main loop, call ImGui::UpdatePlatformWindows(), ImGui::RenderPlatformWindowsDefault() after EndFrame() or Render(). +// You may skip calling RenderPlatformWindowsDefault() if its API is not convenient for your needs. Read comments below. +// - Application: Fix absolute coordinates used in ImGui::SetWindowPos() or ImGui::SetNextWindowPos() calls. +// +// About ImGui::RenderPlatformWindowsDefault(): +// - This function is a mostly a _helper_ for the common-most cases, and to facilitate using default backends. +// - You can check its simple source code to understand what it does. +// It basically iterates secondary viewports and call 4 functions that are setup in ImGuiPlatformIO, if available: +// Platform_RenderWindow(), Renderer_RenderWindow(), Platform_SwapBuffers(), Renderer_SwapBuffers() +// Those functions pointers exists only for the benefit of RenderPlatformWindowsDefault(). +// - If you have very specific rendering needs (e.g. flipping multiple swap-chain simultaneously, unusual sync/threading issues, etc.), +// you may be tempted to ignore RenderPlatformWindowsDefault() and write customized code to perform your renderingg. +// You may decide to setup the platform_io's *RenderWindow and *SwapBuffers pointers and call your functions through those pointers, +// or you may decide to never setup those pointers and call your code directly. They are a convenience, not an obligatory interface. +//----------------------------------------------------------------------------- + +// Access via ImGui::GetPlatformIO() +struct ImGuiPlatformIO +{ + IMGUI_API ImGuiPlatformIO(); + + //------------------------------------------------------------------ + // Interface with OS and Platform backend (basic) + //------------------------------------------------------------------ + + // Optional: Access OS clipboard + // (default to use native Win32 clipboard on Windows, otherwise uses a private clipboard. Override to access OS clipboard on other architectures) + const char* (*Platform_GetClipboardTextFn)(ImGuiContext* ctx); + void (*Platform_SetClipboardTextFn)(ImGuiContext* ctx, const char* text); + void* Platform_ClipboardUserData; + + // Optional: Open link/folder/file in OS Shell + // (default to use ShellExecuteA() on Windows, system() on Linux/Mac) + bool (*Platform_OpenInShellFn)(ImGuiContext* ctx, const char* path); + void* Platform_OpenInShellUserData; + + // Optional: Notify OS Input Method Editor of the screen position of your cursor for text input position (e.g. when using Japanese/Chinese IME on Windows) + // (default to use native imm32 api on Windows) + void (*Platform_SetImeDataFn)(ImGuiContext* ctx, ImGuiViewport* viewport, ImGuiPlatformImeData* data); + void* Platform_ImeUserData; + //void (*SetPlatformImeDataFn)(ImGuiViewport* viewport, ImGuiPlatformImeData* data); // [Renamed to platform_io.PlatformSetImeDataFn in 1.91.1] + + // Optional: Platform locale + // [Experimental] Configure decimal point e.g. '.' or ',' useful for some languages (e.g. German), generally pulled from *localeconv()->decimal_point + ImWchar Platform_LocaleDecimalPoint; // '.' + + //------------------------------------------------------------------ + // Interface with Renderer Backend + //------------------------------------------------------------------ + + // Written by some backends during ImGui_ImplXXXX_RenderDrawData() call to point backend_specific ImGui_ImplXXXX_RenderState* structure. + void* Renderer_RenderState; + + //------------------------------------------------------------------ + // Input - Interface with OS/backends (Multi-Viewport support!) + //------------------------------------------------------------------ + + // For reference, the second column shows which function are generally calling the Platform Functions: + // N = ImGui::NewFrame() ~ beginning of the dear imgui frame: read info from platform/OS windows (latest size/position) + // F = ImGui::Begin(), ImGui::EndFrame() ~ during the dear imgui frame + // U = ImGui::UpdatePlatformWindows() ~ after the dear imgui frame: create and update all platform/OS windows + // R = ImGui::RenderPlatformWindowsDefault() ~ render + // D = ImGui::DestroyPlatformWindows() ~ shutdown + // The general idea is that NewFrame() we will read the current Platform/OS state, and UpdatePlatformWindows() will write to it. + + // The handlers are designed so we can mix and match two imgui_impl_xxxx files, one Platform backend and one Renderer backend. + // Custom engine backends will often provide both Platform and Renderer interfaces together and so may not need to use all functions. + // Platform functions are typically called _before_ their Renderer counterpart, apart from Destroy which are called the other way. + + // Platform Backend functions (e.g. Win32, GLFW, SDL) ------------------- Called by ----- + void (*Platform_CreateWindow)(ImGuiViewport* vp); // . . U . . // Create a new platform window for the given viewport + void (*Platform_DestroyWindow)(ImGuiViewport* vp); // N . U . D // + void (*Platform_ShowWindow)(ImGuiViewport* vp); // . . U . . // Newly created windows are initially hidden so SetWindowPos/Size/Title can be called on them before showing the window + void (*Platform_SetWindowPos)(ImGuiViewport* vp, ImVec2 pos); // . . U . . // Set platform window position (given the upper-left corner of client area) + ImVec2 (*Platform_GetWindowPos)(ImGuiViewport* vp); // N . . . . // + void (*Platform_SetWindowSize)(ImGuiViewport* vp, ImVec2 size); // . . U . . // Set platform window client area size (ignoring OS decorations such as OS title bar etc.) + ImVec2 (*Platform_GetWindowSize)(ImGuiViewport* vp); // N . . . . // Get platform window client area size + void (*Platform_SetWindowFocus)(ImGuiViewport* vp); // N . . . . // Move window to front and set input focus + bool (*Platform_GetWindowFocus)(ImGuiViewport* vp); // . . U . . // + bool (*Platform_GetWindowMinimized)(ImGuiViewport* vp); // N . . . . // Get platform window minimized state. When minimized, we generally won't attempt to get/set size and contents will be culled more easily + void (*Platform_SetWindowTitle)(ImGuiViewport* vp, const char* str); // . . U . . // Set platform window title (given an UTF-8 string) + void (*Platform_SetWindowAlpha)(ImGuiViewport* vp, float alpha); // . . U . . // (Optional) Setup global transparency (not per-pixel transparency) + void (*Platform_UpdateWindow)(ImGuiViewport* vp); // . . U . . // (Optional) Called by UpdatePlatformWindows(). Optional hook to allow the platform backend from doing general book-keeping every frame. + void (*Platform_RenderWindow)(ImGuiViewport* vp, void* render_arg); // . . . R . // (Optional) Main rendering (platform side! This is often unused, or just setting a "current" context for OpenGL bindings). 'render_arg' is the value passed to RenderPlatformWindowsDefault(). + void (*Platform_SwapBuffers)(ImGuiViewport* vp, void* render_arg); // . . . R . // (Optional) Call Present/SwapBuffers (platform side! This is often unused!). 'render_arg' is the value passed to RenderPlatformWindowsDefault(). + float (*Platform_GetWindowDpiScale)(ImGuiViewport* vp); // N . . . . // (Optional) [BETA] FIXME-DPI: DPI handling: Return DPI scale for this viewport. 1.0f = 96 DPI. + void (*Platform_OnChangedViewport)(ImGuiViewport* vp); // . F . . . // (Optional) [BETA] FIXME-DPI: DPI handling: Called during Begin() every time the viewport we are outputting into changes, so backend has a chance to swap fonts to adjust style. + ImVec4 (*Platform_GetWindowWorkAreaInsets)(ImGuiViewport* vp); // N . . . . // (Optional) [BETA] Get initial work area inset for the viewport (won't be covered by main menu bar, dockspace over viewport etc.). Default to (0,0),(0,0). 'safeAreaInsets' in iOS land, 'DisplayCutout' in Android land. + int (*Platform_CreateVkSurface)(ImGuiViewport* vp, ImU64 vk_inst, const void* vk_allocators, ImU64* out_vk_surface); // (Optional) For a Vulkan Renderer to call into Platform code (since the surface creation needs to tie them both). + + // Renderer Backend functions (e.g. DirectX, OpenGL, Vulkan) ------------ Called by ----- + void (*Renderer_CreateWindow)(ImGuiViewport* vp); // . . U . . // Create swap chain, frame buffers etc. (called after Platform_CreateWindow) + void (*Renderer_DestroyWindow)(ImGuiViewport* vp); // N . U . D // Destroy swap chain, frame buffers etc. (called before Platform_DestroyWindow) + void (*Renderer_SetWindowSize)(ImGuiViewport* vp, ImVec2 size); // . . U . . // Resize swap chain, frame buffers etc. (called after Platform_SetWindowSize) + void (*Renderer_RenderWindow)(ImGuiViewport* vp, void* render_arg); // . . . R . // (Optional) Clear framebuffer, setup render target, then render the viewport->DrawData. 'render_arg' is the value passed to RenderPlatformWindowsDefault(). + void (*Renderer_SwapBuffers)(ImGuiViewport* vp, void* render_arg); // . . . R . // (Optional) Call Present/SwapBuffers. 'render_arg' is the value passed to RenderPlatformWindowsDefault(). + + // (Optional) Monitor list + // - Updated by: app/backend. Update every frame to dynamically support changing monitor or DPI configuration. + // - Used by: dear imgui to query DPI info, clamp popups/tooltips within same monitor and not have them straddle monitors. + ImVector Monitors; + + //------------------------------------------------------------------ + // Output - List of viewports to render into platform windows + //------------------------------------------------------------------ + + // Viewports list (the list is updated by calling ImGui::EndFrame or ImGui::Render) + // (in the future we will attempt to organize this feature to remove the need for a "main viewport") + ImVector Viewports; // Main viewports, followed by all secondary viewports. +}; + +// (Optional) This is required when enabling multi-viewport. Represent the bounds of each connected monitor/display and their DPI. +// We use this information for multiple DPI support + clamping the position of popups and tooltips so they don't straddle multiple monitors. +struct ImGuiPlatformMonitor +{ + ImVec2 MainPos, MainSize; // Coordinates of the area displayed on this monitor (Min = upper left, Max = bottom right) + ImVec2 WorkPos, WorkSize; // Coordinates without task bars / side bars / menu bars. Used to avoid positioning popups/tooltips inside this region. If you don't have this info, please copy the value for MainPos/MainSize. + float DpiScale; // 1.0f = 96 DPI + void* PlatformHandle; // Backend dependant data (e.g. HMONITOR, GLFWmonitor*, SDL Display Index, NSScreen*) + ImGuiPlatformMonitor() { MainPos = MainSize = WorkPos = WorkSize = ImVec2(0, 0); DpiScale = 1.0f; PlatformHandle = NULL; } +}; + +// (Optional) Support for IME (Input Method Editor) via the platform_io.Platform_SetImeDataFn() function. struct ImGuiPlatformImeData { bool WantVisible; // A widget wants the IME to be visible @@ -3262,29 +3841,37 @@ struct ImGuiPlatformImeData #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS namespace ImGui { + // OBSOLETED in 1.91.0 (from July 2024) + static inline void PushButtonRepeat(bool repeat) { PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); } + static inline void PopButtonRepeat() { PopItemFlag(); } + static inline void PushTabStop(bool tab_stop) { PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); } + static inline void PopTabStop() { PopItemFlag(); } + IMGUI_API ImVec2 GetContentRegionMax(); // Content boundaries max (e.g. window boundaries including scrolling, or current column boundaries). You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()! + IMGUI_API ImVec2 GetWindowContentRegionMin(); // Content boundaries min for the window (roughly (0,0)-Scroll), in window-local coordinates. You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()! + IMGUI_API ImVec2 GetWindowContentRegionMax(); // Content boundaries max for the window (roughly (0,0)+Size-Scroll), in window-local coordinates. You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()! // OBSOLETED in 1.90.0 (from September 2023) - static inline bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags window_flags = 0) { return BeginChild(id, size, ImGuiChildFlags_FrameStyle, window_flags); } - static inline void EndChildFrame() { EndChild(); } - //static inline bool BeginChild(const char* str_id, const ImVec2& size_arg, bool border, ImGuiWindowFlags window_flags){ return BeginChild(str_id, size_arg, border ? ImGuiChildFlags_Border : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Border - //static inline bool BeginChild(ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags window_flags) { return BeginChild(id, size_arg, border ? ImGuiChildFlags_Border : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Border - static inline void ShowStackToolWindow(bool* p_open = NULL) { ShowIDStackToolWindow(p_open); } - IMGUI_API bool ListBox(const char* label, int* current_item, bool (*old_callback)(void* user_data, int idx, const char** out_text), void* user_data, int items_count, int height_in_items = -1); + static inline bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags window_flags = 0) { return BeginChild(id, size, ImGuiChildFlags_FrameStyle, window_flags); } + static inline void EndChildFrame() { EndChild(); } + //static inline bool BeginChild(const char* str_id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags){ return BeginChild(str_id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders + //static inline bool BeginChild(ImGuiID id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags) { return BeginChild(id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders + static inline void ShowStackToolWindow(bool* p_open = NULL) { ShowIDStackToolWindow(p_open); } IMGUI_API bool Combo(const char* label, int* current_item, bool (*old_callback)(void* user_data, int idx, const char** out_text), void* user_data, int items_count, int popup_max_height_in_items = -1); + IMGUI_API bool ListBox(const char* label, int* current_item, bool (*old_callback)(void* user_data, int idx, const char** out_text), void* user_data, int items_count, int height_in_items = -1); // OBSOLETED in 1.89.7 (from June 2023) - IMGUI_API void SetItemAllowOverlap(); // Use SetNextItemAllowOverlap() before item. + IMGUI_API void SetItemAllowOverlap(); // Use SetNextItemAllowOverlap() before item. // OBSOLETED in 1.89.4 (from March 2023) - static inline void PushAllowKeyboardFocus(bool tab_stop) { PushTabStop(tab_stop); } - static inline void PopAllowKeyboardFocus() { PopTabStop(); } - // OBSOLETED in 1.89 (from August 2022) - IMGUI_API bool ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); // Use new ImageButton() signature (explicit item id, regular FramePadding) - // OBSOLETED in 1.87 (from February 2022 but more formally obsoleted April 2024) - IMGUI_API ImGuiKey GetKeyIndex(ImGuiKey key); // Map ImGuiKey_* values into legacy native key index. == io.KeyMap[key]. When using a 1.87+ backend using io.AddKeyEvent(), calling GetKeyIndex() with ANY ImGuiKey_XXXX values will return the same value! - //static inline ImGuiKey GetKeyIndex(ImGuiKey key) { IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END); return key; } + static inline void PushAllowKeyboardFocus(bool tab_stop) { PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); } + static inline void PopAllowKeyboardFocus() { PopItemFlag(); } // Some of the older obsolete names along with their replacement (commented out so they are not reported in IDE) + //-- OBSOLETED in 1.89 (from August 2022) + //IMGUI_API bool ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); // --> Use new ImageButton() signature (explicit item id, regular FramePadding). Refer to code in 1.91 if you want to grab a copy of this version. //-- OBSOLETED in 1.88 (from May 2022) - //static inline void CaptureKeyboardFromApp(bool want_capture_keyboard = true) { SetNextFrameWantCaptureKeyboard(want_capture_keyboard); } // Renamed as name was misleading + removed default value. - //static inline void CaptureMouseFromApp(bool want_capture_mouse = true) { SetNextFrameWantCaptureMouse(want_capture_mouse); } // Renamed as name was misleading + removed default value. + //static inline void CaptureKeyboardFromApp(bool want_capture_keyboard = true) { SetNextFrameWantCaptureKeyboard(want_capture_keyboard); } // Renamed as name was misleading + removed default value. + //static inline void CaptureMouseFromApp(bool want_capture_mouse = true) { SetNextFrameWantCaptureMouse(want_capture_mouse); } // Renamed as name was misleading + removed default value. + //-- OBSOLETED in 1.87 (from February 2022, more formally obsoleted April 2024) + //IMGUI_API ImGuiKey GetKeyIndex(ImGuiKey key); { IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END); const ImGuiKeyData* key_data = GetKeyData(key); return (ImGuiKey)(key_data - g.IO.KeysData); } // Map ImGuiKey_* values into legacy native key index. == io.KeyMap[key]. When using a 1.87+ backend using io.AddKeyEvent(), calling GetKeyIndex() with ANY ImGuiKey_XXXX values will return the same value! + //static inline ImGuiKey GetKeyIndex(ImGuiKey key) { IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END); return key; } //-- OBSOLETED in 1.86 (from November 2021) //IMGUI_API void CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end); // Code removed, see 1.90 for last version of the code. Calculate range of visible items for large list of evenly sized items. Prefer using ImGuiListClipper. //-- OBSOLETED in 1.85 (from August 2021) @@ -3357,8 +3944,8 @@ namespace ImGui // RENAMED and MERGED both ImGuiKey_ModXXX and ImGuiModFlags_XXX into ImGuiMod_XXX (from September 2022) // RENAMED ImGuiKeyModFlags -> ImGuiModFlags in 1.88 (from April 2022). Exceptionally commented out ahead of obscolescence schedule to reduce confusion and because they were not meant to be used in the first place. -typedef ImGuiKeyChord ImGuiModFlags; // == int. We generally use ImGuiKeyChord to mean "a ImGuiKey or-ed with any number of ImGuiMod_XXX value", but you may store only mods in there. -enum ImGuiModFlags_ { ImGuiModFlags_None = 0, ImGuiModFlags_Ctrl = ImGuiMod_Ctrl, ImGuiModFlags_Shift = ImGuiMod_Shift, ImGuiModFlags_Alt = ImGuiMod_Alt, ImGuiModFlags_Super = ImGuiMod_Super }; +//typedef ImGuiKeyChord ImGuiModFlags; // == int. We generally use ImGuiKeyChord to mean "a ImGuiKey or-ed with any number of ImGuiMod_XXX value", so you may store mods in there. +//enum ImGuiModFlags_ { ImGuiModFlags_None = 0, ImGuiModFlags_Ctrl = ImGuiMod_Ctrl, ImGuiModFlags_Shift = ImGuiMod_Shift, ImGuiModFlags_Alt = ImGuiMod_Alt, ImGuiModFlags_Super = ImGuiMod_Super }; //typedef ImGuiKeyChord ImGuiKeyModFlags; // == int //enum ImGuiKeyModFlags_ { ImGuiKeyModFlags_None = 0, ImGuiKeyModFlags_Ctrl = ImGuiMod_Ctrl, ImGuiKeyModFlags_Shift = ImGuiMod_Shift, ImGuiKeyModFlags_Alt = ImGuiMod_Alt, ImGuiKeyModFlags_Super = ImGuiMod_Super }; @@ -3367,10 +3954,7 @@ enum ImGuiModFlags_ { ImGuiModFlags_None = 0, ImGuiModFlags_Ctrl = ImGuiMod_Ctrl #endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // RENAMED IMGUI_DISABLE_METRICS_WINDOW > IMGUI_DISABLE_DEBUG_TOOLS in 1.88 (from June 2022) -#if defined(IMGUI_DISABLE_METRICS_WINDOW) && !defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && !defined(IMGUI_DISABLE_DEBUG_TOOLS) -#define IMGUI_DISABLE_DEBUG_TOOLS -#endif -#if defined(IMGUI_DISABLE_METRICS_WINDOW) && defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) +#ifdef IMGUI_DISABLE_METRICS_WINDOW #error IMGUI_DISABLE_METRICS_WINDOW was renamed to IMGUI_DISABLE_DEBUG_TOOLS, please use new name. #endif diff --git a/ext/imgui/imgui_demo.cpp b/ext/imgui/imgui_demo.cpp index f9787001e7ae..3ec30279b6a0 100644 --- a/ext/imgui/imgui_demo.cpp +++ b/ext/imgui/imgui_demo.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.9 WIP +// dear imgui, v1.91.6 // (demo code) // Help: @@ -11,7 +11,7 @@ // Get the latest version at https://github.com/ocornut/imgui // How to easily locate code? -// - Use the Item Picker to debug break in code by clicking any widgets: https://github.com/ocornut/imgui/wiki/Debug-Tools +// - Use Tools->Item Picker to debug break in code by clicking any widgets: https://github.com/ocornut/imgui/wiki/Debug-Tools // - Browse an online version the demo with code linked to hovered widgets: https://pthom.github.io/imgui_manual_online/manual/imgui_manual.html // - Find a visible string and search for it in the code! @@ -62,6 +62,7 @@ // - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols inside comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. // - In Visual Studio w/ Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. // - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. +// - You can search/grep for all sections listed in the index to find the section. /* @@ -69,13 +70,15 @@ Index of this file: // [SECTION] Forward Declarations // [SECTION] Helpers +// [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor & Multi-Select demos) // [SECTION] Demo Window / ShowDemoWindow() -// - ShowDemoWindow() -// - sub section: ShowDemoWindowWidgets() -// - sub section: ShowDemoWindowLayout() -// - sub section: ShowDemoWindowPopups() -// - sub section: ShowDemoWindowTables() -// - sub section: ShowDemoWindowInputs() +// [SECTION] ShowDemoWindowMenuBar() +// [SECTION] ShowDemoWindowWidgets() +// [SECTION] ShowDemoWindowMultiSelect() +// [SECTION] ShowDemoWindowLayout() +// [SECTION] ShowDemoWindowPopups() +// [SECTION] ShowDemoWindowTables() +// [SECTION] ShowDemoWindowInputs() // [SECTION] About Window / ShowAboutWindow() // [SECTION] Style Editor / ShowStyleEditor() // [SECTION] User Guide / ShowUserGuide() @@ -91,12 +94,12 @@ Index of this file: // [SECTION] Example App: Fullscreen window / ShowExampleAppFullscreen() // [SECTION] Example App: Manipulating window titles / ShowExampleAppWindowTitles() // [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() +// [SECTION] Example App: Docking, DockSpace / ShowExampleAppDockSpace() // [SECTION] Example App: Documents Handling / ShowExampleAppDocuments() +// [SECTION] Example App: Assets Browser / ShowExampleAppAssetsBrowser() */ -#undef new - #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif @@ -114,6 +117,9 @@ Index of this file: #if !defined(_MSC_VER) || _MSC_VER >= 1800 #include // PRId64/PRIu64, not avail in some MinGW headers. #endif +#ifdef __EMSCRIPTEN__ +#include // __EMSCRIPTEN_major__ etc. +#endif // Visual Studio warnings #ifdef _MSC_VER @@ -140,12 +146,12 @@ Index of this file: #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access #elif defined(__GNUC__) -#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind -#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size -#pragma GCC diagnostic ignored "-Wformat-security" // warning: format string is not a string literal (potentially insecure) -#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function -#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value -#pragma GCC diagnostic ignored "-Wmisleading-indentation" // [__GNUC__ >= 6] warning: this 'if' clause does not guard this statement // GCC 6.0+ only. See #883 on GitHub. +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size +#pragma GCC diagnostic ignored "-Wformat-security" // warning: format string is not a string literal (potentially insecure) +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wmisleading-indentation" // [__GNUC__ >= 6] warning: this 'if' clause does not guard this statement // GCC 6.0+ only. See #883 on GitHub. #endif // Play it nice with Windows users (Update: May 2018, Notepad now supports Unix-style carriage returns!) @@ -191,19 +197,22 @@ Index of this file: #endif //----------------------------------------------------------------------------- -// [SECTION] Forward Declarations, Helpers +// [SECTION] Forward Declarations //----------------------------------------------------------------------------- #if !defined(IMGUI_DISABLE_DEMO_WINDOWS) // Forward Declarations +struct ImGuiDemoWindowData; static void ShowExampleAppMainMenuBar(); +static void ShowExampleAppAssetsBrowser(bool* p_open); static void ShowExampleAppConsole(bool* p_open); static void ShowExampleAppCustomRendering(bool* p_open); +static void ShowExampleAppDockSpace(bool* p_open); static void ShowExampleAppDocuments(bool* p_open); static void ShowExampleAppLog(bool* p_open); static void ShowExampleAppLayout(bool* p_open); -static void ShowExampleAppPropertyEditor(bool* p_open); +static void ShowExampleAppPropertyEditor(bool* p_open, ImGuiDemoWindowData* demo_data); static void ShowExampleAppSimpleOverlay(bool* p_open); static void ShowExampleAppAutoResize(bool* p_open); static void ShowExampleAppConstrainedResize(bool* p_open); @@ -213,8 +222,10 @@ static void ShowExampleAppWindowTitles(bool* p_open); static void ShowExampleMenuFile(); // We split the contents of the big ShowDemoWindow() function into smaller functions -// (because the link time of very large functions grow non-linearly) -static void ShowDemoWindowWidgets(); +// (because the link time of very large functions tends to grow non-linearly) +static void ShowDemoWindowMenuBar(ImGuiDemoWindowData* demo_data); +static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data); +static void ShowDemoWindowMultiSelect(ImGuiDemoWindowData* demo_data); static void ShowDemoWindowLayout(); static void ShowDemoWindowPopups(); static void ShowDemoWindowTables(); @@ -239,6 +250,16 @@ static void HelpMarker(const char* desc) } } +static void ShowDockingDisabledMessage() +{ + ImGuiIO& io = ImGui::GetIO(); + ImGui::Text("ERROR: Docking is not enabled! See Demo > Configuration."); + ImGui::Text("Set io.ConfigFlags |= ImGuiConfigFlags_DockingEnable in your code, or "); + ImGui::SameLine(0.0f, 0.0f); + if (ImGui::SmallButton("click here")) + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; +} + // Helper to wire demo markers located in code to an interactive browser typedef void (*ImGuiDemoMarkerCallback)(const char* file, int line, const char* section, void* user_data); extern ImGuiDemoMarkerCallback GImGuiDemoMarkerCallback; @@ -248,17 +269,132 @@ void* GImGuiDemoMarkerCallbackUserData = NULL; #define IMGUI_DEMO_MARKER(section) do { if (GImGuiDemoMarkerCallback != NULL) GImGuiDemoMarkerCallback(__FILE__, __LINE__, section, GImGuiDemoMarkerCallbackUserData); } while (0) //----------------------------------------------------------------------------- -// [SECTION] Demo Window / ShowDemoWindow() +// [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor etc.) +//----------------------------------------------------------------------------- + +// Simple representation for a tree +// (this is designed to be simple to understand for our demos, not to be fancy or efficient etc.) +struct ExampleTreeNode +{ + // Tree structure + char Name[28] = ""; + int UID = 0; + ExampleTreeNode* Parent = NULL; + ImVector Childs; + unsigned short IndexInParent = 0; // Maintaining this allows us to implement linear traversal more easily + + // Leaf Data + bool HasData = false; // All leaves have data + bool DataMyBool = true; + int DataMyInt = 128; + ImVec2 DataMyVec2 = ImVec2(0.0f, 3.141592f); +}; + +// Simple representation of struct metadata/serialization data. +// (this is a minimal version of what a typical advanced application may provide) +struct ExampleMemberInfo +{ + const char* Name; // Member name + ImGuiDataType DataType; // Member type + int DataCount; // Member count (1 when scalar) + int Offset; // Offset inside parent structure +}; + +// Metadata description of ExampleTreeNode struct. +static const ExampleMemberInfo ExampleTreeNodeMemberInfos[] +{ + { "MyBool", ImGuiDataType_Bool, 1, offsetof(ExampleTreeNode, DataMyBool) }, + { "MyInt", ImGuiDataType_S32, 1, offsetof(ExampleTreeNode, DataMyInt) }, + { "MyVec2", ImGuiDataType_Float, 2, offsetof(ExampleTreeNode, DataMyVec2) }, +}; + +static ExampleTreeNode* ExampleTree_CreateNode(const char* name, int uid, ExampleTreeNode* parent) +{ + ExampleTreeNode* node = IM_NEW(ExampleTreeNode); + snprintf(node->Name, IM_ARRAYSIZE(node->Name), "%s", name); + node->UID = uid; + node->Parent = parent; + node->IndexInParent = parent ? (unsigned short)parent->Childs.Size : 0; + if (parent) + parent->Childs.push_back(node); + return node; +} + +static void ExampleTree_DestroyNode(ExampleTreeNode* node) +{ + for (ExampleTreeNode* child_node : node->Childs) + ExampleTree_DestroyNode(child_node); + IM_DELETE(node); +} + +// Create example tree data +// (this allocates _many_ more times than most other code in either Dear ImGui or others demo) +static ExampleTreeNode* ExampleTree_CreateDemoTree() +{ + static const char* root_names[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pear", "Pineapple", "Strawberry", "Watermelon" }; + const size_t NAME_MAX_LEN = sizeof(ExampleTreeNode::Name); + char name_buf[NAME_MAX_LEN]; + int uid = 0; + ExampleTreeNode* node_L0 = ExampleTree_CreateNode("", ++uid, NULL); + const int root_items_multiplier = 2; + for (int idx_L0 = 0; idx_L0 < IM_ARRAYSIZE(root_names) * root_items_multiplier; idx_L0++) + { + snprintf(name_buf, IM_ARRAYSIZE(name_buf), "%s %d", root_names[idx_L0 / root_items_multiplier], idx_L0 % root_items_multiplier); + ExampleTreeNode* node_L1 = ExampleTree_CreateNode(name_buf, ++uid, node_L0); + const int number_of_childs = (int)strlen(node_L1->Name); + for (int idx_L1 = 0; idx_L1 < number_of_childs; idx_L1++) + { + snprintf(name_buf, IM_ARRAYSIZE(name_buf), "Child %d", idx_L1); + ExampleTreeNode* node_L2 = ExampleTree_CreateNode(name_buf, ++uid, node_L1); + node_L2->HasData = true; + if (idx_L1 == 0) + { + snprintf(name_buf, IM_ARRAYSIZE(name_buf), "Sub-child %d", 0); + ExampleTreeNode* node_L3 = ExampleTree_CreateNode(name_buf, ++uid, node_L2); + node_L3->HasData = true; + } + } + } + return node_L0; +} + //----------------------------------------------------------------------------- -// - ShowDemoWindow() -// - ShowDemoWindowWidgets() -// - ShowDemoWindowLayout() -// - ShowDemoWindowPopups() -// - ShowDemoWindowTables() -// - ShowDemoWindowColumns() -// - ShowDemoWindowInputs() +// [SECTION] Demo Window / ShowDemoWindow() //----------------------------------------------------------------------------- +// Data to be shared across different functions of the demo. +struct ImGuiDemoWindowData +{ + // Examples Apps (accessible from the "Examples" menu) + bool ShowMainMenuBar = false; + bool ShowAppAssetsBrowser = false; + bool ShowAppConsole = false; + bool ShowAppCustomRendering = false; + bool ShowAppDocuments = false; + bool ShowAppDockSpace = false; + bool ShowAppLog = false; + bool ShowAppLayout = false; + bool ShowAppPropertyEditor = false; + bool ShowAppSimpleOverlay = false; + bool ShowAppAutoResize = false; + bool ShowAppConstrainedResize = false; + bool ShowAppFullscreen = false; + bool ShowAppLongText = false; + bool ShowAppWindowTitles = false; + + // Dear ImGui Tools (accessible from the "Tools" menu) + bool ShowMetrics = false; + bool ShowDebugLog = false; + bool ShowIDStackTool = false; + bool ShowStyleEditor = false; + bool ShowAbout = false; + + // Other data + ExampleTreeNode* DemoTree = NULL; + + ~ImGuiDemoWindowData() { if (DemoTree) ExampleTree_DestroyNode(DemoTree); } +}; + // Demonstrate most Dear ImGui features (this is big function!) // You may execute this function to experiment with the UI and understand what it does. // You may then search for keywords in the code when you are interested by a specific feature. @@ -271,56 +407,37 @@ void ImGui::ShowDemoWindow(bool* p_open) // Verify ABI compatibility between caller code and compiled version of Dear ImGui. This helps detects some build issues. IMGUI_CHECKVERSION(); + // Stored data + static ImGuiDemoWindowData demo_data; + // Examples Apps (accessible from the "Examples" menu) - static bool show_app_main_menu_bar = false; - static bool show_app_console = false; - static bool show_app_custom_rendering = false; - static bool show_app_documents = false; - static bool show_app_log = false; - static bool show_app_layout = false; - static bool show_app_property_editor = false; - static bool show_app_simple_overlay = false; - static bool show_app_auto_resize = false; - static bool show_app_constrained_resize = false; - static bool show_app_fullscreen = false; - static bool show_app_long_text = false; - static bool show_app_window_titles = false; - - if (show_app_main_menu_bar) ShowExampleAppMainMenuBar(); - if (show_app_documents) ShowExampleAppDocuments(&show_app_documents); - if (show_app_console) ShowExampleAppConsole(&show_app_console); - if (show_app_custom_rendering) ShowExampleAppCustomRendering(&show_app_custom_rendering); - if (show_app_log) ShowExampleAppLog(&show_app_log); - if (show_app_layout) ShowExampleAppLayout(&show_app_layout); - if (show_app_property_editor) ShowExampleAppPropertyEditor(&show_app_property_editor); - if (show_app_simple_overlay) ShowExampleAppSimpleOverlay(&show_app_simple_overlay); - if (show_app_auto_resize) ShowExampleAppAutoResize(&show_app_auto_resize); - if (show_app_constrained_resize) ShowExampleAppConstrainedResize(&show_app_constrained_resize); - if (show_app_fullscreen) ShowExampleAppFullscreen(&show_app_fullscreen); - if (show_app_long_text) ShowExampleAppLongText(&show_app_long_text); - if (show_app_window_titles) ShowExampleAppWindowTitles(&show_app_window_titles); + if (demo_data.ShowMainMenuBar) { ShowExampleAppMainMenuBar(); } + if (demo_data.ShowAppDockSpace) { ShowExampleAppDockSpace(&demo_data.ShowAppDockSpace); } // Important: Process the Docking app first, as explicit DockSpace() nodes needs to be submitted early (read comments near the DockSpace function) + if (demo_data.ShowAppDocuments) { ShowExampleAppDocuments(&demo_data.ShowAppDocuments); } // ...process the Document app next, as it may also use a DockSpace() + if (demo_data.ShowAppAssetsBrowser) { ShowExampleAppAssetsBrowser(&demo_data.ShowAppAssetsBrowser); } + if (demo_data.ShowAppConsole) { ShowExampleAppConsole(&demo_data.ShowAppConsole); } + if (demo_data.ShowAppCustomRendering) { ShowExampleAppCustomRendering(&demo_data.ShowAppCustomRendering); } + if (demo_data.ShowAppLog) { ShowExampleAppLog(&demo_data.ShowAppLog); } + if (demo_data.ShowAppLayout) { ShowExampleAppLayout(&demo_data.ShowAppLayout); } + if (demo_data.ShowAppPropertyEditor) { ShowExampleAppPropertyEditor(&demo_data.ShowAppPropertyEditor, &demo_data); } + if (demo_data.ShowAppSimpleOverlay) { ShowExampleAppSimpleOverlay(&demo_data.ShowAppSimpleOverlay); } + if (demo_data.ShowAppAutoResize) { ShowExampleAppAutoResize(&demo_data.ShowAppAutoResize); } + if (demo_data.ShowAppConstrainedResize) { ShowExampleAppConstrainedResize(&demo_data.ShowAppConstrainedResize); } + if (demo_data.ShowAppFullscreen) { ShowExampleAppFullscreen(&demo_data.ShowAppFullscreen); } + if (demo_data.ShowAppLongText) { ShowExampleAppLongText(&demo_data.ShowAppLongText); } + if (demo_data.ShowAppWindowTitles) { ShowExampleAppWindowTitles(&demo_data.ShowAppWindowTitles); } // Dear ImGui Tools (accessible from the "Tools" menu) - static bool show_tool_metrics = false; - static bool show_tool_debug_log = false; - static bool show_tool_id_stack_tool = false; - static bool show_tool_style_editor = false; - static bool show_tool_about = false; - - if (show_tool_metrics) - ImGui::ShowMetricsWindow(&show_tool_metrics); - if (show_tool_debug_log) - ImGui::ShowDebugLogWindow(&show_tool_debug_log); - if (show_tool_id_stack_tool) - ImGui::ShowIDStackToolWindow(&show_tool_id_stack_tool); - if (show_tool_style_editor) - { - ImGui::Begin("Dear ImGui Style Editor", &show_tool_style_editor); + if (demo_data.ShowMetrics) { ImGui::ShowMetricsWindow(&demo_data.ShowMetrics); } + if (demo_data.ShowDebugLog) { ImGui::ShowDebugLogWindow(&demo_data.ShowDebugLog); } + if (demo_data.ShowIDStackTool) { ImGui::ShowIDStackToolWindow(&demo_data.ShowIDStackTool); } + if (demo_data.ShowAbout) { ImGui::ShowAboutWindow(&demo_data.ShowAbout); } + if (demo_data.ShowStyleEditor) + { + ImGui::Begin("Dear ImGui Style Editor", &demo_data.ShowStyleEditor); ImGui::ShowStyleEditor(); ImGui::End(); } - if (show_tool_about) - ImGui::ShowAboutWindow(&show_tool_about); // Demonstrate the various window flags. Typically you would just use the default! static bool no_titlebar = false; @@ -333,6 +450,7 @@ void ImGui::ShowDemoWindow(bool* p_open) static bool no_nav = false; static bool no_background = false; static bool no_bring_to_front = false; + static bool no_docking = false; static bool unsaved_document = false; ImGuiWindowFlags window_flags = 0; @@ -345,6 +463,7 @@ void ImGui::ShowDemoWindow(bool* p_open) if (no_nav) window_flags |= ImGuiWindowFlags_NoNav; if (no_background) window_flags |= ImGuiWindowFlags_NoBackground; if (no_bring_to_front) window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus; + if (no_docking) window_flags |= ImGuiWindowFlags_NoDocking; if (unsaved_document) window_flags |= ImGuiWindowFlags_UnsavedDocument; if (no_close) p_open = NULL; // Don't pass our bool* to Begin @@ -363,67 +482,11 @@ void ImGui::ShowDemoWindow(bool* p_open) } // Most "big" widgets share a common width settings by default. See 'Demo->Layout->Widgets Width' for details. - // e.g. Use 2/3 of the space for widgets and 1/3 for labels (right align) - //ImGui::PushItemWidth(-ImGui::GetWindowWidth() * 0.35f); - // e.g. Leave a fixed amount of width for labels (by passing a negative value), the rest goes to widgets. - ImGui::PushItemWidth(ImGui::GetFontSize() * -12); + ImGui::PushItemWidth(ImGui::GetFontSize() * -12); // e.g. Leave a fixed amount of width for labels (by passing a negative value), the rest goes to widgets. + //ImGui::PushItemWidth(-ImGui::GetWindowWidth() * 0.35f); // e.g. Use 2/3 of the space for widgets and 1/3 for labels (right align) // Menu Bar - if (ImGui::BeginMenuBar()) - { - if (ImGui::BeginMenu("Menu")) - { - IMGUI_DEMO_MARKER("Menu/File"); - ShowExampleMenuFile(); - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Examples")) - { - IMGUI_DEMO_MARKER("Menu/Examples"); - ImGui::MenuItem("Main menu bar", NULL, &show_app_main_menu_bar); - - ImGui::SeparatorText("Mini apps"); - ImGui::MenuItem("Console", NULL, &show_app_console); - ImGui::MenuItem("Custom rendering", NULL, &show_app_custom_rendering); - ImGui::MenuItem("Documents", NULL, &show_app_documents); - ImGui::MenuItem("Log", NULL, &show_app_log); - ImGui::MenuItem("Property editor", NULL, &show_app_property_editor); - ImGui::MenuItem("Simple layout", NULL, &show_app_layout); - ImGui::MenuItem("Simple overlay", NULL, &show_app_simple_overlay); - - ImGui::SeparatorText("Concepts"); - ImGui::MenuItem("Auto-resizing window", NULL, &show_app_auto_resize); - ImGui::MenuItem("Constrained-resizing window", NULL, &show_app_constrained_resize); - ImGui::MenuItem("Fullscreen window", NULL, &show_app_fullscreen); - ImGui::MenuItem("Long text display", NULL, &show_app_long_text); - ImGui::MenuItem("Manipulating window titles", NULL, &show_app_window_titles); - - ImGui::EndMenu(); - } - //if (ImGui::MenuItem("MenuItem")) {} // You can also use MenuItem() inside a menu bar! - if (ImGui::BeginMenu("Tools")) - { - IMGUI_DEMO_MARKER("Menu/Tools"); -#ifndef IMGUI_DISABLE_DEBUG_TOOLS - const bool has_debug_tools = true; -#else - const bool has_debug_tools = false; -#endif - ImGui::MenuItem("Metrics/Debugger", NULL, &show_tool_metrics, has_debug_tools); - ImGui::MenuItem("Debug Log", NULL, &show_tool_debug_log, has_debug_tools); - ImGui::MenuItem("ID Stack Tool", NULL, &show_tool_id_stack_tool, has_debug_tools); - ImGui::MenuItem("Style Editor", NULL, &show_tool_style_editor); - bool is_debugger_present = ImGui::GetIO().ConfigDebugIsDebuggerPresent; - if (ImGui::MenuItem("Item Picker", NULL, false, has_debug_tools && is_debugger_present)) - ImGui::DebugStartItemPicker(); - if (!is_debugger_present) - ImGui::SetItemTooltip("Requires io.ConfigDebugIsDebuggerPresent=true to be set.\n\nWe otherwise disable the menu option to avoid casual users crashing the application.\n\nYou can however always access the Item Picker in Metrics->Tools."); - ImGui::Separator(); - ImGui::MenuItem("About Dear ImGui", NULL, &show_tool_about); - ImGui::EndMenu(); - } - ImGui::EndMenuBar(); - } + ShowDemoWindowMenuBar(&demo_data); ImGui::Text("dear imgui says hello! (%s) (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); ImGui::Spacing(); @@ -441,7 +504,9 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::BulletText("See the ShowDemoWindow() code in imgui_demo.cpp. <- you are here!"); ImGui::BulletText("See comments in imgui.cpp."); ImGui::BulletText("See example applications in the examples/ folder."); - ImGui::BulletText("Read the FAQ at https://www.dearimgui.com/faq/"); + ImGui::BulletText("Read the FAQ at "); + ImGui::SameLine(0, 0); + ImGui::TextLinkOpenURL("https://www.dearimgui.com/faq/"); ImGui::BulletText("Set 'io.ConfigFlags |= NavEnableKeyboard' for keyboard controls."); ImGui::BulletText("Set 'io.ConfigFlags |= NavEnableGamepad' for gamepad controls."); @@ -461,27 +526,93 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::SameLine(); HelpMarker("Enable keyboard controls."); ImGui::CheckboxFlags("io.ConfigFlags: NavEnableGamepad", &io.ConfigFlags, ImGuiConfigFlags_NavEnableGamepad); ImGui::SameLine(); HelpMarker("Enable gamepad controls. Require backend to set io.BackendFlags |= ImGuiBackendFlags_HasGamepad.\n\nRead instructions in imgui.cpp for details."); - ImGui::CheckboxFlags("io.ConfigFlags: NavEnableSetMousePos", &io.ConfigFlags, ImGuiConfigFlags_NavEnableSetMousePos); - ImGui::SameLine(); HelpMarker("Instruct navigation to move the mouse cursor. See comment for ImGuiConfigFlags_NavEnableSetMousePos."); ImGui::CheckboxFlags("io.ConfigFlags: NoMouse", &io.ConfigFlags, ImGuiConfigFlags_NoMouse); + ImGui::SameLine(); HelpMarker("Instruct dear imgui to disable mouse inputs and interactions."); + + // The "NoMouse" option can get us stuck with a disabled mouse! Let's provide an alternative way to fix it: if (io.ConfigFlags & ImGuiConfigFlags_NoMouse) { - // The "NoMouse" option can get us stuck with a disabled mouse! Let's provide an alternative way to fix it: if (fmodf((float)ImGui::GetTime(), 0.40f) < 0.20f) { ImGui::SameLine(); ImGui::Text("<>"); } - if (ImGui::IsKeyPressed(ImGuiKey_Space)) + // Prevent both being checked + if (ImGui::IsKeyPressed(ImGuiKey_Space) || (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard)) io.ConfigFlags &= ~ImGuiConfigFlags_NoMouse; } - ImGui::CheckboxFlags("io.ConfigFlags: NoMouseCursorChange", &io.ConfigFlags, ImGuiConfigFlags_NoMouseCursorChange); + + ImGui::CheckboxFlags("io.ConfigFlags: NoMouseCursorChange", &io.ConfigFlags, ImGuiConfigFlags_NoMouseCursorChange); ImGui::SameLine(); HelpMarker("Instruct backend to not alter mouse cursor shape and visibility."); + ImGui::CheckboxFlags("io.ConfigFlags: NoKeyboard", &io.ConfigFlags, ImGuiConfigFlags_NoKeyboard); + ImGui::SameLine(); HelpMarker("Instruct dear imgui to disable keyboard inputs and interactions."); + ImGui::Checkbox("io.ConfigInputTrickleEventQueue", &io.ConfigInputTrickleEventQueue); ImGui::SameLine(); HelpMarker("Enable input queue trickling: some types of events submitted during the same frame (e.g. button down + up) will be spread over multiple frames, improving interactions with low framerates."); ImGui::Checkbox("io.MouseDrawCursor", &io.MouseDrawCursor); ImGui::SameLine(); HelpMarker("Instruct Dear ImGui to render a mouse cursor itself. Note that a mouse cursor rendered via your application GPU rendering path will feel more laggy than hardware cursor, but will be more in sync with your other visuals.\n\nSome desktop applications may use both kinds of cursors (e.g. enable software cursor only when resizing/dragging something)."); + ImGui::SeparatorText("Keyboard/Gamepad Navigation"); + ImGui::Checkbox("io.ConfigNavSwapGamepadButtons", &io.ConfigNavSwapGamepadButtons); + ImGui::Checkbox("io.ConfigNavMoveSetMousePos", &io.ConfigNavMoveSetMousePos); + ImGui::SameLine(); HelpMarker("Directional/tabbing navigation teleports the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is difficult"); + ImGui::Checkbox("io.ConfigNavCaptureKeyboard", &io.ConfigNavCaptureKeyboard); + ImGui::Checkbox("io.ConfigNavEscapeClearFocusItem", &io.ConfigNavEscapeClearFocusItem); + ImGui::SameLine(); HelpMarker("Pressing Escape clears focused item."); + ImGui::Checkbox("io.ConfigNavEscapeClearFocusWindow", &io.ConfigNavEscapeClearFocusWindow); + ImGui::SameLine(); HelpMarker("Pressing Escape clears focused window."); + ImGui::Checkbox("io.ConfigNavCursorVisibleAuto", &io.ConfigNavCursorVisibleAuto); + ImGui::SameLine(); HelpMarker("Using directional navigation key makes the cursor visible. Mouse click hides the cursor."); + ImGui::Checkbox("io.ConfigNavCursorVisibleAlways", &io.ConfigNavCursorVisibleAlways); + ImGui::SameLine(); HelpMarker("Navigation cursor is always visible."); + + ImGui::SeparatorText("Docking"); + ImGui::CheckboxFlags("io.ConfigFlags: DockingEnable", &io.ConfigFlags, ImGuiConfigFlags_DockingEnable); + ImGui::SameLine(); + if (io.ConfigDockingWithShift) + HelpMarker("Drag from window title bar or their tab to dock/undock. Hold SHIFT to enable docking.\n\nDrag from window menu button (upper-left button) to undock an entire node (all windows)."); + else + HelpMarker("Drag from window title bar or their tab to dock/undock. Hold SHIFT to disable docking.\n\nDrag from window menu button (upper-left button) to undock an entire node (all windows)."); + if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) + { + ImGui::Indent(); + ImGui::Checkbox("io.ConfigDockingNoSplit", &io.ConfigDockingNoSplit); + ImGui::SameLine(); HelpMarker("Simplified docking mode: disable window splitting, so docking is limited to merging multiple windows together into tab-bars."); + ImGui::Checkbox("io.ConfigDockingWithShift", &io.ConfigDockingWithShift); + ImGui::SameLine(); HelpMarker("Enable docking when holding Shift only (allow to drop in wider space, reduce visual noise)"); + ImGui::Checkbox("io.ConfigDockingAlwaysTabBar", &io.ConfigDockingAlwaysTabBar); + ImGui::SameLine(); HelpMarker("Create a docking node and tab-bar on single floating windows."); + ImGui::Checkbox("io.ConfigDockingTransparentPayload", &io.ConfigDockingTransparentPayload); + ImGui::SameLine(); HelpMarker("Make window or viewport transparent when docking and only display docking boxes on the target viewport. Useful if rendering of multiple viewport cannot be synced. Best used with ConfigViewportsNoAutoMerge."); + ImGui::Unindent(); + } + + ImGui::SeparatorText("Multi-viewports"); + ImGui::CheckboxFlags("io.ConfigFlags: ViewportsEnable", &io.ConfigFlags, ImGuiConfigFlags_ViewportsEnable); + ImGui::SameLine(); HelpMarker("[beta] Enable beta multi-viewports support. See ImGuiPlatformIO for details."); + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + ImGui::Indent(); + ImGui::Checkbox("io.ConfigViewportsNoAutoMerge", &io.ConfigViewportsNoAutoMerge); + ImGui::SameLine(); HelpMarker("Set to make all floating imgui windows always create their own viewport. Otherwise, they are merged into the main host viewports when overlapping it."); + ImGui::Checkbox("io.ConfigViewportsNoTaskBarIcon", &io.ConfigViewportsNoTaskBarIcon); + ImGui::SameLine(); HelpMarker("Toggling this at runtime is normally unsupported (most platform backends won't refresh the task bar icon state right away)."); + ImGui::Checkbox("io.ConfigViewportsNoDecoration", &io.ConfigViewportsNoDecoration); + ImGui::SameLine(); HelpMarker("Toggling this at runtime is normally unsupported (most platform backends won't refresh the decoration right away)."); + ImGui::Checkbox("io.ConfigViewportsNoDefaultParent", &io.ConfigViewportsNoDefaultParent); + ImGui::SameLine(); HelpMarker("Toggling this at runtime is normally unsupported (most platform backends won't refresh the parenting right away)."); + ImGui::Unindent(); + } + + ImGui::SeparatorText("Windows"); + ImGui::Checkbox("io.ConfigWindowsResizeFromEdges", &io.ConfigWindowsResizeFromEdges); + ImGui::SameLine(); HelpMarker("Enable resizing of windows from their edges and from the lower-left corner.\nThis requires ImGuiBackendFlags_HasMouseCursors for better mouse cursor feedback."); + ImGui::Checkbox("io.ConfigWindowsMoveFromTitleBarOnly", &io.ConfigWindowsMoveFromTitleBarOnly); + ImGui::Checkbox("io.ConfigWindowsCopyContentsWithCtrlC", &io.ConfigWindowsCopyContentsWithCtrlC); // [EXPERIMENTAL] + ImGui::SameLine(); HelpMarker("*EXPERIMENTAL* CTRL+C copy the contents of focused window into the clipboard.\n\nExperimental because:\n- (1) has known issues with nested Begin/End pairs.\n- (2) text output quality varies.\n- (3) text output is in submission order rather than spatial order."); + ImGui::Checkbox("io.ConfigScrollbarScrollByPage", &io.ConfigScrollbarScrollByPage); + ImGui::SameLine(); HelpMarker("Enable scrolling page by page when clicking outside the scrollbar grab.\nWhen disabled, always scroll to clicked location.\nWhen enabled, Shift+Click scrolls to clicked location."); + ImGui::SeparatorText("Widgets"); ImGui::Checkbox("io.ConfigInputTextCursorBlink", &io.ConfigInputTextCursorBlink); ImGui::SameLine(); HelpMarker("Enable blinking cursor (optional as some users consider it to be distracting)."); @@ -489,18 +620,35 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::SameLine(); HelpMarker("Pressing Enter will keep item active and select contents (single-line only)."); ImGui::Checkbox("io.ConfigDragClickToInputText", &io.ConfigDragClickToInputText); ImGui::SameLine(); HelpMarker("Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving)."); - ImGui::Checkbox("io.ConfigWindowsResizeFromEdges", &io.ConfigWindowsResizeFromEdges); - ImGui::SameLine(); HelpMarker("Enable resizing of windows from their edges and from the lower-left corner.\nThis requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback."); - ImGui::Checkbox("io.ConfigWindowsMoveFromTitleBarOnly", &io.ConfigWindowsMoveFromTitleBarOnly); ImGui::Checkbox("io.ConfigMacOSXBehaviors", &io.ConfigMacOSXBehaviors); ImGui::SameLine(); HelpMarker("Swap Cmd<>Ctrl keys, enable various MacOS style behaviors."); ImGui::Text("Also see Style->Rendering for rendering options."); + // Also read: https://github.com/ocornut/imgui/wiki/Error-Handling + ImGui::SeparatorText("Error Handling"); + + ImGui::Checkbox("io.ConfigErrorRecovery", &io.ConfigErrorRecovery); + ImGui::SameLine(); HelpMarker( + "Options to configure how we handle recoverable errors.\n" + "- Error recovery is not perfect nor guaranteed! It is a feature to ease development.\n" + "- You not are not supposed to rely on it in the course of a normal application run.\n" + "- Possible usage: facilitate recovery from errors triggered from a scripting language or after specific exceptions handlers.\n" + "- Always ensure that on programmers seat you have at minimum Asserts or Tooltips enabled when making direct imgui API call!" + "Otherwise it would severely hinder your ability to catch and correct mistakes!"); + ImGui::Checkbox("io.ConfigErrorRecoveryEnableAssert", &io.ConfigErrorRecoveryEnableAssert); + ImGui::Checkbox("io.ConfigErrorRecoveryEnableDebugLog", &io.ConfigErrorRecoveryEnableDebugLog); + ImGui::Checkbox("io.ConfigErrorRecoveryEnableTooltip", &io.ConfigErrorRecoveryEnableTooltip); + if (!io.ConfigErrorRecoveryEnableAssert && !io.ConfigErrorRecoveryEnableDebugLog && !io.ConfigErrorRecoveryEnableTooltip) + io.ConfigErrorRecoveryEnableAssert = io.ConfigErrorRecoveryEnableDebugLog = io.ConfigErrorRecoveryEnableTooltip = true; + + // Also read: https://github.com/ocornut/imgui/wiki/Debug-Tools ImGui::SeparatorText("Debug"); ImGui::Checkbox("io.ConfigDebugIsDebuggerPresent", &io.ConfigDebugIsDebuggerPresent); ImGui::SameLine(); HelpMarker("Enable various tools calling IM_DEBUG_BREAK().\n\nRequires a debugger being attached, otherwise IM_DEBUG_BREAK() options will appear to crash your application."); + ImGui::Checkbox("io.ConfigDebugHighlightIdConflicts", &io.ConfigDebugHighlightIdConflicts); + ImGui::SameLine(); HelpMarker("Highlight and show an error message when multiple items have conflicting identifiers."); ImGui::BeginDisabled(); - ImGui::Checkbox("io.ConfigDebugBeginReturnValueOnce", &io.ConfigDebugBeginReturnValueOnce); // . + ImGui::Checkbox("io.ConfigDebugBeginReturnValueOnce", &io.ConfigDebugBeginReturnValueOnce); ImGui::EndDisabled(); ImGui::SameLine(); HelpMarker("First calls to Begin()/BeginChild() will return false.\n\nTHIS OPTION IS DISABLED because it needs to be set at application boot-time to make sense. Showing the disabled option is a way to make this feature easier to discover."); ImGui::Checkbox("io.ConfigDebugBeginReturnValueLoop", &io.ConfigDebugBeginReturnValueLoop); @@ -521,13 +669,18 @@ void ImGui::ShowDemoWindow(bool* p_open) "Those flags are set by the backends (imgui_impl_xxx files) to specify their capabilities.\n" "Here we expose them as read-only fields to avoid breaking interactions with your backend."); + // Make a local copy to avoid modifying actual backend flags. // FIXME: Maybe we need a BeginReadonly() equivalent to keep label bright? ImGui::BeginDisabled(); - ImGui::CheckboxFlags("io.BackendFlags: HasGamepad", &io.BackendFlags, ImGuiBackendFlags_HasGamepad); - ImGui::CheckboxFlags("io.BackendFlags: HasMouseCursors", &io.BackendFlags, ImGuiBackendFlags_HasMouseCursors); - ImGui::CheckboxFlags("io.BackendFlags: HasSetMousePos", &io.BackendFlags, ImGuiBackendFlags_HasSetMousePos); - ImGui::CheckboxFlags("io.BackendFlags: RendererHasVtxOffset", &io.BackendFlags, ImGuiBackendFlags_RendererHasVtxOffset); + ImGui::CheckboxFlags("io.BackendFlags: HasGamepad", &io.BackendFlags, ImGuiBackendFlags_HasGamepad); + ImGui::CheckboxFlags("io.BackendFlags: HasMouseCursors", &io.BackendFlags, ImGuiBackendFlags_HasMouseCursors); + ImGui::CheckboxFlags("io.BackendFlags: HasSetMousePos", &io.BackendFlags, ImGuiBackendFlags_HasSetMousePos); + ImGui::CheckboxFlags("io.BackendFlags: PlatformHasViewports", &io.BackendFlags, ImGuiBackendFlags_PlatformHasViewports); + ImGui::CheckboxFlags("io.BackendFlags: HasMouseHoveredViewport",&io.BackendFlags, ImGuiBackendFlags_HasMouseHoveredViewport); + ImGui::CheckboxFlags("io.BackendFlags: RendererHasVtxOffset", &io.BackendFlags, ImGuiBackendFlags_RendererHasVtxOffset); + ImGui::CheckboxFlags("io.BackendFlags: RendererHasViewports", &io.BackendFlags, ImGuiBackendFlags_RendererHasViewports); ImGui::EndDisabled(); + ImGui::TreePop(); ImGui::Spacing(); } @@ -535,8 +688,9 @@ void ImGui::ShowDemoWindow(bool* p_open) IMGUI_DEMO_MARKER("Configuration/Style"); if (ImGui::TreeNode("Style")) { + ImGui::Checkbox("Style Editor", &demo_data.ShowStyleEditor); + ImGui::SameLine(); HelpMarker("The same contents can be accessed in 'Tools->Style Editor' or by calling the ShowStyleEditor() function."); - ImGui::ShowStyleEditor(); ImGui::TreePop(); ImGui::Spacing(); } @@ -576,13 +730,14 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::TableNextColumn(); ImGui::Checkbox("No nav", &no_nav); ImGui::TableNextColumn(); ImGui::Checkbox("No background", &no_background); ImGui::TableNextColumn(); ImGui::Checkbox("No bring to front", &no_bring_to_front); + ImGui::TableNextColumn(); ImGui::Checkbox("No docking", &no_docking); ImGui::TableNextColumn(); ImGui::Checkbox("Unsaved document", &unsaved_document); ImGui::EndTable(); } } // All demo contents - ShowDemoWindowWidgets(); + ShowDemoWindowWidgets(&demo_data); ShowDemoWindowLayout(); ShowDemoWindowPopups(); ShowDemoWindowTables(); @@ -593,9 +748,83 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::End(); } -static void ShowDemoWindowWidgets() +//----------------------------------------------------------------------------- +// [SECTION] ShowDemoWindowMenuBar() +//----------------------------------------------------------------------------- + +static void ShowDemoWindowMenuBar(ImGuiDemoWindowData* demo_data) +{ + IMGUI_DEMO_MARKER("Menu"); + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Menu")) + { + IMGUI_DEMO_MARKER("Menu/File"); + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Examples")) + { + IMGUI_DEMO_MARKER("Menu/Examples"); + ImGui::MenuItem("Main menu bar", NULL, &demo_data->ShowMainMenuBar); + + ImGui::SeparatorText("Mini apps"); + ImGui::MenuItem("Assets Browser", NULL, &demo_data->ShowAppAssetsBrowser); + ImGui::MenuItem("Console", NULL, &demo_data->ShowAppConsole); + ImGui::MenuItem("Custom rendering", NULL, &demo_data->ShowAppCustomRendering); + ImGui::MenuItem("Documents", NULL, &demo_data->ShowAppDocuments); + ImGui::MenuItem("Dockspace", NULL, &demo_data->ShowAppDockSpace); + ImGui::MenuItem("Log", NULL, &demo_data->ShowAppLog); + ImGui::MenuItem("Property editor", NULL, &demo_data->ShowAppPropertyEditor); + ImGui::MenuItem("Simple layout", NULL, &demo_data->ShowAppLayout); + ImGui::MenuItem("Simple overlay", NULL, &demo_data->ShowAppSimpleOverlay); + + ImGui::SeparatorText("Concepts"); + ImGui::MenuItem("Auto-resizing window", NULL, &demo_data->ShowAppAutoResize); + ImGui::MenuItem("Constrained-resizing window", NULL, &demo_data->ShowAppConstrainedResize); + ImGui::MenuItem("Fullscreen window", NULL, &demo_data->ShowAppFullscreen); + ImGui::MenuItem("Long text display", NULL, &demo_data->ShowAppLongText); + ImGui::MenuItem("Manipulating window titles", NULL, &demo_data->ShowAppWindowTitles); + + ImGui::EndMenu(); + } + //if (ImGui::MenuItem("MenuItem")) {} // You can also use MenuItem() inside a menu bar! + if (ImGui::BeginMenu("Tools")) + { + IMGUI_DEMO_MARKER("Menu/Tools"); + ImGuiIO& io = ImGui::GetIO(); +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + const bool has_debug_tools = true; +#else + const bool has_debug_tools = false; +#endif + ImGui::MenuItem("Metrics/Debugger", NULL, &demo_data->ShowMetrics, has_debug_tools); + ImGui::MenuItem("Debug Log", NULL, &demo_data->ShowDebugLog, has_debug_tools); + ImGui::MenuItem("ID Stack Tool", NULL, &demo_data->ShowIDStackTool, has_debug_tools); + bool is_debugger_present = io.ConfigDebugIsDebuggerPresent; + if (ImGui::MenuItem("Item Picker", NULL, false, has_debug_tools && is_debugger_present)) + ImGui::DebugStartItemPicker(); + if (!is_debugger_present) + ImGui::SetItemTooltip("Requires io.ConfigDebugIsDebuggerPresent=true to be set.\n\nWe otherwise disable the menu option to avoid casual users crashing the application.\n\nYou can however always access the Item Picker in Metrics->Tools."); + ImGui::MenuItem("Style Editor", NULL, &demo_data->ShowStyleEditor); + ImGui::MenuItem("About Dear ImGui", NULL, &demo_data->ShowAbout); + + ImGui::SeparatorText("Debug Options"); + ImGui::MenuItem("Highlight ID Conflicts", NULL, &io.ConfigDebugHighlightIdConflicts, has_debug_tools); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } +} + +//----------------------------------------------------------------------------- +// [SECTION] ShowDemoWindowWidgets() +//----------------------------------------------------------------------------- + +static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data) { IMGUI_DEMO_MARKER("Widgets"); + //ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (!ImGui::CollapsingHeader("Widgets")) return; @@ -654,11 +883,11 @@ static void ShowDemoWindowWidgets() IMGUI_DEMO_MARKER("Widgets/Basic/Buttons (Repeating)"); static int counter = 0; float spacing = ImGui::GetStyle().ItemInnerSpacing.x; - ImGui::PushButtonRepeat(true); + ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); if (ImGui::ArrowButton("##left", ImGuiDir_Left)) { counter--; } ImGui::SameLine(0.0f, spacing); if (ImGui::ArrowButton("##right", ImGuiDir_Right)) { counter++; } - ImGui::PopButtonRepeat(); + ImGui::PopItemFlag(); ImGui::SameLine(); ImGui::Text("%d", counter); @@ -715,18 +944,19 @@ static void ShowDemoWindowWidgets() { IMGUI_DEMO_MARKER("Widgets/Basic/DragInt, DragFloat"); - static int i1 = 50, i2 = 42; + static int i1 = 50, i2 = 42, i3 = 128; ImGui::DragInt("drag int", &i1, 1); ImGui::SameLine(); HelpMarker( "Click and drag to edit value.\n" "Hold SHIFT/ALT for faster/slower edit.\n" "Double-click or CTRL+click to input value."); - ImGui::DragInt("drag int 0..100", &i2, 1, 0, 100, "%d%%", ImGuiSliderFlags_AlwaysClamp); + ImGui::DragInt("drag int wrap 100..200", &i3, 1, 100, 200, "%d", ImGuiSliderFlags_WrapAround); static float f1 = 1.00f, f2 = 0.0067f; ImGui::DragFloat("drag float", &f1, 0.005f); ImGui::DragFloat("drag small float", &f2, 0.0001f, 0.0f, 0.0f, "%.06f ns"); + //ImGui::DragFloat("drag wrap -1..1", &f3, 0.005f, -1.0f, 1.0f, NULL, ImGuiSliderFlags_WrapAround); } ImGui::SeparatorText("Sliders"); @@ -859,7 +1089,7 @@ static void ShowDemoWindowWidgets() // The following examples are passed for documentation purpose but may not be useful to most users. // Passing ImGuiHoveredFlags_ForTooltip to IsItemHovered() will pull ImGuiHoveredFlags flags values from - // 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav' depending on whether mouse or gamepad/keyboard is being used. + // 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav' depending on whether mouse or keyboard/gamepad is being used. // With default settings, ImGuiHoveredFlags_ForTooltip is equivalent to ImGuiHoveredFlags_DelayShort + ImGuiHoveredFlags_Stationary. ImGui::Button("Manual", sz); if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) @@ -883,12 +1113,11 @@ static void ShowDemoWindowWidgets() // Using ImGuiHoveredFlags_ForTooltip will pull flags from 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav', // which default value include the ImGuiHoveredFlags_AllowWhenDisabled flag. - // As a result, Set ImGui::BeginDisabled(); ImGui::Button("Disabled item", sz); - ImGui::EndDisabled(); if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) ImGui::SetTooltip("I am a a tooltip for a disabled item."); + ImGui::EndDisabled(); ImGui::TreePop(); } @@ -945,6 +1174,7 @@ static void ShowDemoWindowWidgets() ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAllColumns", &base_flags, ImGuiTreeNodeFlags_SpanAllColumns); ImGui::SameLine(); HelpMarker("For use in Tables only."); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_AllowOverlap", &base_flags, ImGuiTreeNodeFlags_AllowOverlap); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_Framed", &base_flags, ImGuiTreeNodeFlags_Framed); ImGui::SameLine(); HelpMarker("Draw frame with background (e.g. for CollapsingHeader)"); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_NavLeftJumpsBackHere", &base_flags, ImGuiTreeNodeFlags_NavLeftJumpsBackHere); ImGui::Checkbox("Align label with current X position", &align_label_with_current_x_position); ImGui::Checkbox("Test tree node as drag source", &test_drag_and_drop); ImGui::Text("Hello!"); @@ -977,7 +1207,7 @@ static void ShowDemoWindowWidgets() ImGui::Text("This is a drag and drop source"); ImGui::EndDragDropSource(); } - if (i == 2) + if (i == 2 && (base_flags & ImGuiTreeNodeFlags_SpanTextWidth)) { // Item 2 has an additional inline button to help demonstrate SpanTextWidth. ImGui::SameLine(); @@ -986,6 +1216,8 @@ static void ShowDemoWindowWidgets() if (node_open) { ImGui::BulletText("Blah blah\nBlah Blah"); + ImGui::SameLine(); + ImGui::SmallButton("Button"); ImGui::TreePop(); } } @@ -1247,18 +1479,18 @@ static void ShowDemoWindowWidgets() // (your selection data could be an index, a pointer to the object, an id for the object, a flag intrusively // stored in the object itself, etc.) const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" }; - static int item_current_idx = 0; // Here we store our selection data as an index. + static int item_selected_idx = 0; // Here we store our selection data as an index. // Pass in the preview value visible before opening the combo (it could technically be different contents or not pulled from items[]) - const char* combo_preview_value = items[item_current_idx]; + const char* combo_preview_value = items[item_selected_idx]; if (ImGui::BeginCombo("combo 1", combo_preview_value, flags)) { for (int n = 0; n < IM_ARRAYSIZE(items); n++) { - const bool is_selected = (item_current_idx == n); + const bool is_selected = (item_selected_idx == n); if (ImGui::Selectable(items[n], is_selected)) - item_current_idx = n; + item_selected_idx = n; // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) if (is_selected) @@ -1300,14 +1532,22 @@ static void ShowDemoWindowWidgets() // (your selection data could be an index, a pointer to the object, an id for the object, a flag intrusively // stored in the object itself, etc.) const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" }; - static int item_current_idx = 0; // Here we store our selection data as an index. + static int item_selected_idx = 0; // Here we store our selected data as an index. + + static bool item_highlight = false; + int item_highlighted_idx = -1; // Here we store our highlighted data as an index. + ImGui::Checkbox("Highlight hovered item in second listbox", &item_highlight); + if (ImGui::BeginListBox("listbox 1")) { for (int n = 0; n < IM_ARRAYSIZE(items); n++) { - const bool is_selected = (item_current_idx == n); + const bool is_selected = (item_selected_idx == n); if (ImGui::Selectable(items[n], is_selected)) - item_current_idx = n; + item_selected_idx = n; + + if (item_highlight && ImGui::IsItemHovered()) + item_highlighted_idx = n; // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) if (is_selected) @@ -1323,9 +1563,10 @@ static void ShowDemoWindowWidgets() { for (int n = 0; n < IM_ARRAYSIZE(items); n++) { - const bool is_selected = (item_current_idx == n); - if (ImGui::Selectable(items[n], is_selected)) - item_current_idx = n; + bool is_selected = (item_selected_idx == n); + ImGuiSelectableFlags flags = (item_highlighted_idx == n) ? ImGuiSelectableFlags_Highlight : 0; + if (ImGui::Selectable(items[n], is_selected, flags)) + item_selected_idx = n; // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) if (is_selected) @@ -1338,6 +1579,7 @@ static void ShowDemoWindowWidgets() } IMGUI_DEMO_MARKER("Widgets/Selectables"); + //ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (ImGui::TreeNode("Selectables")) { // Selectable() has 2 overloads: @@ -1359,37 +1601,6 @@ static void ShowDemoWindowWidgets() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Selectables/Single Selection"); - if (ImGui::TreeNode("Selection State: Single Selection")) - { - static int selected = -1; - for (int n = 0; n < 5; n++) - { - char buf[32]; - sprintf(buf, "Object %d", n); - if (ImGui::Selectable(buf, selected == n)) - selected = n; - } - ImGui::TreePop(); - } - IMGUI_DEMO_MARKER("Widgets/Selectables/Multiple Selection"); - if (ImGui::TreeNode("Selection State: Multiple Selection")) - { - HelpMarker("Hold CTRL and click to select multiple items."); - static bool selection[5] = { false, false, false, false, false }; - for (int n = 0; n < 5; n++) - { - char buf[32]; - sprintf(buf, "Object %d", n); - if (ImGui::Selectable(buf, selection[n])) - { - if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held - memset(selection, 0, sizeof(selection)); - selection[n] ^= 1; - } - } - ImGui::TreePop(); - } IMGUI_DEMO_MARKER("Widgets/Selectables/Rendering more items on the same line"); if (ImGui::TreeNode("Rendering more items on the same line")) { @@ -1402,8 +1613,8 @@ static void ShowDemoWindowWidgets() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Selectables/In columns"); - if (ImGui::TreeNode("In columns")) + IMGUI_DEMO_MARKER("Widgets/Selectables/In Tables"); + if (ImGui::TreeNode("In Tables")) { static bool selected[10] = {}; @@ -1497,6 +1708,8 @@ static void ShowDemoWindowWidgets() ImGui::TreePop(); } + ShowDemoWindowMultiSelect(demo_data); + // To wire InputText() with std::string or any other custom string type, // see the "Text Input > Resize Callback" section of this demo, and the misc/cpp/imgui_stdlib.h file. IMGUI_DEMO_MARKER("Widgets/Text Input"); @@ -1677,6 +1890,16 @@ static void ShowDemoWindowWidgets() ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Text Input/Eliding, Alignment"); + if (ImGui::TreeNode("Eliding, Alignment")) + { + static char buf1[128] = "/path/to/some/folder/with/long/filename.cpp"; + static ImGuiInputTextFlags flags = ImGuiInputTextFlags_ElideLeft; + ImGui::CheckboxFlags("ImGuiInputTextFlags_ElideLeft", &flags, ImGuiInputTextFlags_ElideLeft); + ImGui::InputText("Path", buf1, IM_ARRAYSIZE(buf1), flags); + ImGui::TreePop(); + } + IMGUI_DEMO_MARKER("Widgets/Text Input/Miscellaneous"); if (ImGui::TreeNode("Miscellaneous")) { @@ -1732,6 +1955,7 @@ static void ShowDemoWindowWidgets() ImGui::CheckboxFlags("ImGuiTabBarFlags_AutoSelectNewTabs", &tab_bar_flags, ImGuiTabBarFlags_AutoSelectNewTabs); ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", &tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton); ImGui::CheckboxFlags("ImGuiTabBarFlags_NoCloseWithMiddleMouseButton", &tab_bar_flags, ImGuiTabBarFlags_NoCloseWithMiddleMouseButton); + ImGui::CheckboxFlags("ImGuiTabBarFlags_DrawSelectedOverline", &tab_bar_flags, ImGuiTabBarFlags_DrawSelectedOverline); if ((tab_bar_flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0) tab_bar_flags |= ImGuiTabBarFlags_FittingPolicyDefault_; if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown)) @@ -1740,11 +1964,13 @@ static void ShowDemoWindowWidgets() tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll); // Tab Bar + ImGui::AlignTextToFramePadding(); + ImGui::Text("Opened:"); const char* names[4] = { "Artichoke", "Beetroot", "Celery", "Daikon" }; static bool opened[4] = { true, true, true, true }; // Persistent user state for (int n = 0; n < IM_ARRAYSIZE(opened); n++) { - if (n > 0) { ImGui::SameLine(); } + ImGui::SameLine(); ImGui::Checkbox(names[n], &opened[n]); } @@ -1895,8 +2121,12 @@ static void ShowDemoWindowWidgets() ImGui::SameLine(); ImGui::SliderInt("Sample count", &display_count, 1, 400); float (*func)(void*, int) = (func_type == 0) ? Funcs::Sin : Funcs::Saw; - ImGui::PlotLines("Lines", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80)); - ImGui::PlotHistogram("Histogram", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80)); + ImGui::PlotLines("Lines##2", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80)); + ImGui::PlotHistogram("Histogram##2", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80)); + ImGui::Separator(); + + ImGui::Text("Need better plotting and graphing? Consider using ImPlot:"); + ImGui::TextLinkOpenURL("https://github.com/epezent/implot"); ImGui::Separator(); ImGui::TreePop(); @@ -2130,13 +2360,18 @@ static void ShowDemoWindowWidgets() // Demonstrate using advanced flags for DragXXX and SliderXXX functions. Note that the flags are the same! static ImGuiSliderFlags flags = ImGuiSliderFlags_None; ImGui::CheckboxFlags("ImGuiSliderFlags_AlwaysClamp", &flags, ImGuiSliderFlags_AlwaysClamp); - ImGui::SameLine(); HelpMarker("Always clamp value to min/max bounds (if any) when input manually with CTRL+Click."); + ImGui::CheckboxFlags("ImGuiSliderFlags_ClampOnInput", &flags, ImGuiSliderFlags_ClampOnInput); + ImGui::SameLine(); HelpMarker("Clamp value to min/max bounds when input manually with CTRL+Click. By default CTRL+Click allows going out of bounds."); + ImGui::CheckboxFlags("ImGuiSliderFlags_ClampZeroRange", &flags, ImGuiSliderFlags_ClampZeroRange); + ImGui::SameLine(); HelpMarker("Clamp even if min==max==0.0f. Otherwise DragXXX functions don't clamp."); ImGui::CheckboxFlags("ImGuiSliderFlags_Logarithmic", &flags, ImGuiSliderFlags_Logarithmic); ImGui::SameLine(); HelpMarker("Enable logarithmic editing (more precision for small values)."); ImGui::CheckboxFlags("ImGuiSliderFlags_NoRoundToFormat", &flags, ImGuiSliderFlags_NoRoundToFormat); ImGui::SameLine(); HelpMarker("Disable rounding underlying value to match precision of the format string (e.g. %.3f values are rounded to those 3 digits)."); ImGui::CheckboxFlags("ImGuiSliderFlags_NoInput", &flags, ImGuiSliderFlags_NoInput); ImGui::SameLine(); HelpMarker("Disable CTRL+Click or Enter key allowing to input text directly into the widget."); + ImGui::CheckboxFlags("ImGuiSliderFlags_WrapAround", &flags, ImGuiSliderFlags_WrapAround); + ImGui::SameLine(); HelpMarker("Enable wrapping around from max to min and from min to max (only supported by DragXXX() functions)"); // Drags static float drag_f = 0.5f; @@ -2146,14 +2381,17 @@ static void ShowDemoWindowWidgets() ImGui::DragFloat("DragFloat (0 -> +inf)", &drag_f, 0.005f, 0.0f, FLT_MAX, "%.3f", flags); ImGui::DragFloat("DragFloat (-inf -> 1)", &drag_f, 0.005f, -FLT_MAX, 1.0f, "%.3f", flags); ImGui::DragFloat("DragFloat (-inf -> +inf)", &drag_f, 0.005f, -FLT_MAX, +FLT_MAX, "%.3f", flags); + //ImGui::DragFloat("DragFloat (0 -> 0)", &drag_f, 0.005f, 0.0f, 0.0f, "%.3f", flags); // To test ClampZeroRange + //ImGui::DragFloat("DragFloat (100 -> 100)", &drag_f, 0.005f, 100.0f, 100.0f, "%.3f", flags); ImGui::DragInt("DragInt (0 -> 100)", &drag_i, 0.5f, 0, 100, "%d", flags); // Sliders static float slider_f = 0.5f; static int slider_i = 50; + const ImGuiSliderFlags flags_for_sliders = flags & ~ImGuiSliderFlags_WrapAround; ImGui::Text("Underlying float value: %f", slider_f); - ImGui::SliderFloat("SliderFloat (0 -> 1)", &slider_f, 0.0f, 1.0f, "%.3f", flags); - ImGui::SliderInt("SliderInt (0 -> 100)", &slider_i, 0, 100, "%d", flags); + ImGui::SliderFloat("SliderFloat (0 -> 1)", &slider_f, 0.0f, 1.0f, "%.3f", flags_for_sliders); + ImGui::SliderInt("SliderInt (0 -> 100)", &slider_i, 0, 100, "%d", flags_for_sliders); ImGui::TreePop(); } @@ -2484,6 +2722,11 @@ static void ShowDemoWindowWidgets() IMGUI_DEMO_MARKER("Widgets/Drag and Drop/Drag to reorder items (simple)"); if (ImGui::TreeNode("Drag to reorder items (simple)")) { + // FIXME: there is temporary (usually single-frame) ID Conflict during reordering as a same item may be submitting twice. + // This code was always slightly faulty but in a way which was not easily noticeable. + // Until we fix this, enable ImGuiItemFlags_AllowDuplicateId to disable detecting the issue. + ImGui::PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); + // Simple reordering HelpMarker( "We don't use the drag and drop api at all here! " @@ -2505,6 +2748,8 @@ static void ShowDemoWindowWidgets() } } } + + ImGui::PopItemFlag(); ImGui::TreePop(); } @@ -2564,7 +2809,7 @@ static void ShowDemoWindowWidgets() ImGui::BeginDisabled(true); if (item_type == 0) { ImGui::Text("ITEM: Text"); } // Testing text items with no identifier/interaction if (item_type == 1) { ret = ImGui::Button("ITEM: Button"); } // Testing button - if (item_type == 2) { ImGui::PushButtonRepeat(true); ret = ImGui::Button("ITEM: Button"); ImGui::PopButtonRepeat(); } // Testing button (with repeater) + if (item_type == 2) { ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); ret = ImGui::Button("ITEM: Button"); ImGui::PopItemFlag(); } // Testing button (with repeater) if (item_type == 3) { ret = ImGui::Checkbox("ITEM: Checkbox", &b); } // Testing checkbox if (item_type == 4) { ret = ImGui::SliderFloat("ITEM: SliderFloat", &col4f[0], 0.0f, 1.0f); } // Testing basic item if (item_type == 5) { ret = ImGui::InputText("ITEM: InputText", &str[0], IM_ARRAYSIZE(str)); } // Testing input text (which handles tabbing) @@ -2657,25 +2902,31 @@ static void ShowDemoWindowWidgets() static bool embed_all_inside_a_child_window = false; ImGui::Checkbox("Embed everything inside a child window for testing _RootWindow flag.", &embed_all_inside_a_child_window); if (embed_all_inside_a_child_window) - ImGui::BeginChild("outer_child", ImVec2(0, ImGui::GetFontSize() * 20.0f), ImGuiChildFlags_Border); + ImGui::BeginChild("outer_child", ImVec2(0, ImGui::GetFontSize() * 20.0f), ImGuiChildFlags_Borders); // Testing IsWindowFocused() function with its various flags. ImGui::BulletText( "IsWindowFocused() = %d\n" "IsWindowFocused(_ChildWindows) = %d\n" "IsWindowFocused(_ChildWindows|_NoPopupHierarchy) = %d\n" + "IsWindowFocused(_ChildWindows|_DockHierarchy) = %d\n" "IsWindowFocused(_ChildWindows|_RootWindow) = %d\n" "IsWindowFocused(_ChildWindows|_RootWindow|_NoPopupHierarchy) = %d\n" + "IsWindowFocused(_ChildWindows|_RootWindow|_DockHierarchy) = %d\n" "IsWindowFocused(_RootWindow) = %d\n" "IsWindowFocused(_RootWindow|_NoPopupHierarchy) = %d\n" + "IsWindowFocused(_RootWindow|_DockHierarchy) = %d\n" "IsWindowFocused(_AnyWindow) = %d\n", ImGui::IsWindowFocused(), ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows), ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_NoPopupHierarchy), + ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_DockHierarchy), ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_RootWindow), ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_NoPopupHierarchy), + ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_DockHierarchy), ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow), ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_NoPopupHierarchy), + ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_DockHierarchy), ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow)); // Testing IsWindowHovered() function with its various flags. @@ -2685,10 +2936,13 @@ static void ShowDemoWindowWidgets() "IsWindowHovered(_AllowWhenBlockedByActiveItem) = %d\n" "IsWindowHovered(_ChildWindows) = %d\n" "IsWindowHovered(_ChildWindows|_NoPopupHierarchy) = %d\n" + "IsWindowHovered(_ChildWindows|_DockHierarchy) = %d\n" "IsWindowHovered(_ChildWindows|_RootWindow) = %d\n" "IsWindowHovered(_ChildWindows|_RootWindow|_NoPopupHierarchy) = %d\n" + "IsWindowHovered(_ChildWindows|_RootWindow|_DockHierarchy) = %d\n" "IsWindowHovered(_RootWindow) = %d\n" "IsWindowHovered(_RootWindow|_NoPopupHierarchy) = %d\n" + "IsWindowHovered(_RootWindow|_DockHierarchy) = %d\n" "IsWindowHovered(_ChildWindows|_AllowWhenBlockedByPopup) = %d\n" "IsWindowHovered(_AnyWindow) = %d\n" "IsWindowHovered(_Stationary) = %d\n", @@ -2697,15 +2951,18 @@ static void ShowDemoWindowWidgets() ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows), ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_NoPopupHierarchy), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_DockHierarchy), ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow), ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_NoPopupHierarchy), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_DockHierarchy), ImGui::IsWindowHovered(ImGuiHoveredFlags_RootWindow), ImGui::IsWindowHovered(ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_NoPopupHierarchy), + ImGui::IsWindowHovered(ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_DockHierarchy), ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_AllowWhenBlockedByPopup), ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow), ImGui::IsWindowHovered(ImGuiHoveredFlags_Stationary)); - ImGui::BeginChild("child", ImVec2(0, 50), ImGuiChildFlags_Border); + ImGui::BeginChild("child", ImVec2(0, 50), ImGuiChildFlags_Borders); ImGui::Text("This is another child window for testing the _ChildWindows flag."); ImGui::EndChild(); if (embed_all_inside_a_child_window) @@ -2713,10 +2970,13 @@ static void ShowDemoWindowWidgets() // Calling IsItemHovered() after begin returns the hovered status of the title bar. // This is useful in particular if you want to create a context menu associated to the title bar of a window. + // This will also work when docked into a Tab (the Tab replace the Title Bar and guarantee the same properties). static bool test_window = false; ImGui::Checkbox("Hovered/Active tests after Begin() for title bar testing", &test_window); if (test_window) { + // FIXME-DOCK: This window cannot be docked within the ImGui Demo window, this will cause a feedback loop and get them stuck. + // Could we fix this through an ImGuiWindowClass feature? Or an API call to tag our parent as "don't skip items"? ImGui::Begin("Title bar Hovered/Active tests", &test_window); if (ImGui::BeginPopupContextItem()) // <-- This is using IsItemHovered() { @@ -2767,55 +3027,1026 @@ static void ShowDemoWindowWidgets() } } -static void ShowDemoWindowLayout() +static const char* ExampleNames[] = { - IMGUI_DEMO_MARKER("Layout"); - if (!ImGui::CollapsingHeader("Layout & Scrolling")) - return; + "Artichoke", "Arugula", "Asparagus", "Avocado", "Bamboo Shoots", "Bean Sprouts", "Beans", "Beet", "Belgian Endive", "Bell Pepper", + "Bitter Gourd", "Bok Choy", "Broccoli", "Brussels Sprouts", "Burdock Root", "Cabbage", "Calabash", "Capers", "Carrot", "Cassava", + "Cauliflower", "Celery", "Celery Root", "Celcuce", "Chayote", "Chinese Broccoli", "Corn", "Cucumber" +}; - IMGUI_DEMO_MARKER("Layout/Child windows"); - if (ImGui::TreeNode("Child windows")) +// Extra functions to add deletion support to ImGuiSelectionBasicStorage +struct ExampleSelectionWithDeletion : ImGuiSelectionBasicStorage +{ + // Find which item should be Focused after deletion. + // Call _before_ item submission. Retunr an index in the before-deletion item list, your item loop should call SetKeyboardFocusHere() on it. + // The subsequent ApplyDeletionPostLoop() code will use it to apply Selection. + // - We cannot provide this logic in core Dear ImGui because we don't have access to selection data. + // - We don't actually manipulate the ImVector<> here, only in ApplyDeletionPostLoop(), but using similar API for consistency and flexibility. + // - Important: Deletion only works if the underlying ImGuiID for your items are stable: aka not depend on their index, but on e.g. item id/ptr. + // FIXME-MULTISELECT: Doesn't take account of the possibility focus target will be moved during deletion. Need refocus or scroll offset. + int ApplyDeletionPreLoop(ImGuiMultiSelectIO* ms_io, int items_count) { - ImGui::SeparatorText("Child windows"); + if (Size == 0) + return -1; - HelpMarker("Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window."); - static bool disable_mouse_wheel = false; - static bool disable_menu = false; - ImGui::Checkbox("Disable Mouse Wheel", &disable_mouse_wheel); - ImGui::Checkbox("Disable Menu", &disable_menu); + // If focused item is not selected... + const int focused_idx = (int)ms_io->NavIdItem; // Index of currently focused item + if (ms_io->NavIdSelected == false) // This is merely a shortcut, == Contains(adapter->IndexToStorage(items, focused_idx)) + { + ms_io->RangeSrcReset = true; // Request to recover RangeSrc from NavId next frame. Would be ok to reset even when NavIdSelected==true, but it would take an extra frame to recover RangeSrc when deleting a selected item. + return focused_idx; // Request to focus same item after deletion. + } - // Child 1: no border, enable horizontal scrollbar + // If focused item is selected: land on first unselected item after focused item. + for (int idx = focused_idx + 1; idx < items_count; idx++) + if (!Contains(GetStorageIdFromIndex(idx))) + return idx; + + // If focused item is selected: otherwise return last unselected item before focused item. + for (int idx = IM_MIN(focused_idx, items_count) - 1; idx >= 0; idx--) + if (!Contains(GetStorageIdFromIndex(idx))) + return idx; + + return -1; + } + + // Rewrite item list (delete items) + update selection. + // - Call after EndMultiSelect() + // - We cannot provide this logic in core Dear ImGui because we don't have access to your items, nor to selection data. + template + void ApplyDeletionPostLoop(ImGuiMultiSelectIO* ms_io, ImVector& items, int item_curr_idx_to_select) + { + // Rewrite item list (delete items) + convert old selection index (before deletion) to new selection index (after selection). + // If NavId was not part of selection, we will stay on same item. + ImVector new_items; + new_items.reserve(items.Size - Size); + int item_next_idx_to_select = -1; + for (int idx = 0; idx < items.Size; idx++) { - ImGuiWindowFlags window_flags = ImGuiWindowFlags_HorizontalScrollbar; - if (disable_mouse_wheel) - window_flags |= ImGuiWindowFlags_NoScrollWithMouse; - ImGui::BeginChild("ChildL", ImVec2(ImGui::GetContentRegionAvail().x * 0.5f, 260), ImGuiChildFlags_None, window_flags); - for (int i = 0; i < 100; i++) - ImGui::Text("%04d: scrollable region", i); - ImGui::EndChild(); + if (!Contains(GetStorageIdFromIndex(idx))) + new_items.push_back(items[idx]); + if (item_curr_idx_to_select == idx) + item_next_idx_to_select = new_items.Size - 1; } + items.swap(new_items); - ImGui::SameLine(); + // Update selection + Clear(); + if (item_next_idx_to_select != -1 && ms_io->NavIdSelected) + SetItemSelected(GetStorageIdFromIndex(item_next_idx_to_select), true); + } +}; - // Child 2: rounded border +// Example: Implement dual list box storage and interface +struct ExampleDualListBox +{ + ImVector Items[2]; // ID is index into ExampleName[] + ImGuiSelectionBasicStorage Selections[2]; // Store ExampleItemId into selection + bool OptKeepSorted = true; + + void MoveAll(int src, int dst) + { + IM_ASSERT((src == 0 && dst == 1) || (src == 1 && dst == 0)); + for (ImGuiID item_id : Items[src]) + Items[dst].push_back(item_id); + Items[src].clear(); + SortItems(dst); + Selections[src].Swap(Selections[dst]); + Selections[src].Clear(); + } + void MoveSelected(int src, int dst) + { + for (int src_n = 0; src_n < Items[src].Size; src_n++) { - ImGuiWindowFlags window_flags = ImGuiWindowFlags_None; - if (disable_mouse_wheel) - window_flags |= ImGuiWindowFlags_NoScrollWithMouse; - if (!disable_menu) - window_flags |= ImGuiWindowFlags_MenuBar; - ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f); - ImGui::BeginChild("ChildR", ImVec2(0, 260), ImGuiChildFlags_Border, window_flags); - if (!disable_menu && ImGui::BeginMenuBar()) + ImGuiID item_id = Items[src][src_n]; + if (!Selections[src].Contains(item_id)) + continue; + Items[src].erase(&Items[src][src_n]); // FIXME-OPT: Could be implemented more optimally (rebuild src items and swap) + Items[dst].push_back(item_id); + src_n--; + } + if (OptKeepSorted) + SortItems(dst); + Selections[src].Swap(Selections[dst]); + Selections[src].Clear(); + } + void ApplySelectionRequests(ImGuiMultiSelectIO* ms_io, int side) + { + // In this example we store item id in selection (instead of item index) + Selections[side].UserData = Items[side].Data; + Selections[side].AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { ImGuiID* items = (ImGuiID*)self->UserData; return items[idx]; }; + Selections[side].ApplyRequests(ms_io); + } + static int IMGUI_CDECL CompareItemsByValue(const void* lhs, const void* rhs) + { + const int* a = (const int*)lhs; + const int* b = (const int*)rhs; + return (*a - *b) > 0 ? +1 : -1; + } + void SortItems(int n) + { + qsort(Items[n].Data, (size_t)Items[n].Size, sizeof(Items[n][0]), CompareItemsByValue); + } + void Show() + { + //ImGui::Checkbox("Sorted", &OptKeepSorted); + if (ImGui::BeginTable("split", 3, ImGuiTableFlags_None)) + { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); // Left side + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); // Buttons + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); // Right side + ImGui::TableNextRow(); + + int request_move_selected = -1; + int request_move_all = -1; + float child_height_0 = 0.0f; + for (int side = 0; side < 2; side++) { - if (ImGui::BeginMenu("Menu")) + // FIXME-MULTISELECT: Dual List Box: Add context menus + // FIXME-NAV: Using ImGuiWindowFlags_NavFlattened exhibit many issues. + ImVector& items = Items[side]; + ImGuiSelectionBasicStorage& selection = Selections[side]; + + ImGui::TableSetColumnIndex((side == 0) ? 0 : 2); + ImGui::Text("%s (%d)", (side == 0) ? "Available" : "Basket", items.Size); + + // Submit scrolling range to avoid glitches on moving/deletion + const float items_height = ImGui::GetTextLineHeightWithSpacing(); + ImGui::SetNextWindowContentSize(ImVec2(0.0f, items.Size * items_height)); + + bool child_visible; + if (side == 0) { - ShowExampleMenuFile(); - ImGui::EndMenu(); + // Left child is resizable + ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, ImGui::GetFrameHeightWithSpacing() * 4), ImVec2(FLT_MAX, FLT_MAX)); + child_visible = ImGui::BeginChild("0", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY); + child_height_0 = ImGui::GetWindowSize().y; } - ImGui::EndMenuBar(); - } - if (ImGui::BeginTable("split", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings)) + else + { + // Right child use same height as left one + child_visible = ImGui::BeginChild("1", ImVec2(-FLT_MIN, child_height_0), ImGuiChildFlags_FrameStyle); + } + if (child_visible) + { + ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_None; + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size); + ApplySelectionRequests(ms_io, side); + + for (int item_n = 0; item_n < items.Size; item_n++) + { + ImGuiID item_id = items[item_n]; + bool item_is_selected = selection.Contains(item_id); + ImGui::SetNextItemSelectionUserData(item_n); + ImGui::Selectable(ExampleNames[item_id], item_is_selected, ImGuiSelectableFlags_AllowDoubleClick); + if (ImGui::IsItemFocused()) + { + // FIXME-MULTISELECT: Dual List Box: Transfer focus + if (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter)) + request_move_selected = side; + if (ImGui::IsMouseDoubleClicked(0)) // FIXME-MULTISELECT: Double-click on multi-selection? + request_move_selected = side; + } + } + + ms_io = ImGui::EndMultiSelect(); + ApplySelectionRequests(ms_io, side); + } + ImGui::EndChild(); + } + + // Buttons columns + ImGui::TableSetColumnIndex(1); + ImGui::NewLine(); + //ImVec2 button_sz = { ImGui::CalcTextSize(">>").x + ImGui::GetStyle().FramePadding.x * 2.0f, ImGui::GetFrameHeight() + padding.y * 2.0f }; + ImVec2 button_sz = { ImGui::GetFrameHeight(), ImGui::GetFrameHeight() }; + + // (Using BeginDisabled()/EndDisabled() works but feels distracting given how it is currently visualized) + if (ImGui::Button(">>", button_sz)) + request_move_all = 0; + if (ImGui::Button(">", button_sz)) + request_move_selected = 0; + if (ImGui::Button("<", button_sz)) + request_move_selected = 1; + if (ImGui::Button("<<", button_sz)) + request_move_all = 1; + + // Process requests + if (request_move_all != -1) + MoveAll(request_move_all, request_move_all ^ 1); + if (request_move_selected != -1) + MoveSelected(request_move_selected, request_move_selected ^ 1); + + // FIXME-MULTISELECT: Support action from outside + /* + if (OptKeepSorted == false) + { + ImGui::NewLine(); + if (ImGui::ArrowButton("MoveUp", ImGuiDir_Up)) {} + if (ImGui::ArrowButton("MoveDown", ImGuiDir_Down)) {} + } + */ + + ImGui::EndTable(); + } + } +}; + +//----------------------------------------------------------------------------- +// [SECTION] ShowDemoWindowMultiSelect() +//----------------------------------------------------------------------------- +// Multi-selection demos +// Also read: https://github.com/ocornut/imgui/wiki/Multi-Select +//----------------------------------------------------------------------------- + +static void ShowDemoWindowMultiSelect(ImGuiDemoWindowData* demo_data) +{ + IMGUI_DEMO_MARKER("Widgets/Selection State & Multi-Select"); + if (ImGui::TreeNode("Selection State & Multi-Select")) + { + HelpMarker("Selections can be built using Selectable(), TreeNode() or other widgets. Selection state is owned by application code/data."); + + // Without any fancy API: manage single-selection yourself. + IMGUI_DEMO_MARKER("Widgets/Selection State/Single-Select"); + if (ImGui::TreeNode("Single-Select")) + { + static int selected = -1; + for (int n = 0; n < 5; n++) + { + char buf[32]; + sprintf(buf, "Object %d", n); + if (ImGui::Selectable(buf, selected == n)) + selected = n; + } + ImGui::TreePop(); + } + + // Demonstrate implementation a most-basic form of multi-selection manually + // This doesn't support the SHIFT modifier which requires BeginMultiSelect()! + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (manual/simplified, without BeginMultiSelect)"); + if (ImGui::TreeNode("Multi-Select (manual/simplified, without BeginMultiSelect)")) + { + HelpMarker("Hold CTRL and click to select multiple items."); + static bool selection[5] = { false, false, false, false, false }; + for (int n = 0; n < 5; n++) + { + char buf[32]; + sprintf(buf, "Object %d", n); + if (ImGui::Selectable(buf, selection[n])) + { + if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held + memset(selection, 0, sizeof(selection)); + selection[n] ^= 1; // Toggle current item + } + } + ImGui::TreePop(); + } + + // Demonstrate handling proper multi-selection using the BeginMultiSelect/EndMultiSelect API. + // SHIFT+Click w/ CTRL and other standard features are supported. + // We use the ImGuiSelectionBasicStorage helper which you may freely reimplement. + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select"); + if (ImGui::TreeNode("Multi-Select")) + { + ImGui::Text("Supported features:"); + ImGui::BulletText("Keyboard navigation (arrows, page up/down, home/end, space)."); + ImGui::BulletText("Ctrl modifier to preserve and toggle selection."); + ImGui::BulletText("Shift modifier for range selection."); + ImGui::BulletText("CTRL+A to select all."); + ImGui::BulletText("Escape to clear selection."); + ImGui::BulletText("Click and drag to box-select."); + ImGui::Text("Tip: Use 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen."); + + // Use default selection.Adapter: Pass index to SetNextItemSelectionUserData(), store index in Selection + const int ITEMS_COUNT = 50; + static ImGuiSelectionBasicStorage selection; + ImGui::Text("Selection: %d/%d", selection.Size, ITEMS_COUNT); + + // The BeginChild() has no purpose for selection logic, other that offering a scrolling region. + if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) + { + ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, ITEMS_COUNT); + selection.ApplyRequests(ms_io); + + for (int n = 0; n < ITEMS_COUNT; n++) + { + char label[64]; + sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]); + bool item_is_selected = selection.Contains((ImGuiID)n); + ImGui::SetNextItemSelectionUserData(n); + ImGui::Selectable(label, item_is_selected); + } + + ms_io = ImGui::EndMultiSelect(); + selection.ApplyRequests(ms_io); + } + ImGui::EndChild(); + ImGui::TreePop(); + } + + // Demonstrate using the clipper with BeginMultiSelect()/EndMultiSelect() + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (with clipper)"); + if (ImGui::TreeNode("Multi-Select (with clipper)")) + { + // Use default selection.Adapter: Pass index to SetNextItemSelectionUserData(), store index in Selection + static ImGuiSelectionBasicStorage selection; + + ImGui::Text("Added features:"); + ImGui::BulletText("Using ImGuiListClipper."); + + const int ITEMS_COUNT = 10000; + ImGui::Text("Selection: %d/%d", selection.Size, ITEMS_COUNT); + if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) + { + ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, ITEMS_COUNT); + selection.ApplyRequests(ms_io); + + ImGuiListClipper clipper; + clipper.Begin(ITEMS_COUNT); + if (ms_io->RangeSrcItem != -1) + clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped. + while (clipper.Step()) + { + for (int n = clipper.DisplayStart; n < clipper.DisplayEnd; n++) + { + char label[64]; + sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]); + bool item_is_selected = selection.Contains((ImGuiID)n); + ImGui::SetNextItemSelectionUserData(n); + ImGui::Selectable(label, item_is_selected); + } + } + + ms_io = ImGui::EndMultiSelect(); + selection.ApplyRequests(ms_io); + } + ImGui::EndChild(); + ImGui::TreePop(); + } + + // Demonstrate dynamic item list + deletion support using the BeginMultiSelect/EndMultiSelect API. + // In order to support Deletion without any glitches you need to: + // - (1) If items are submitted in their own scrolling area, submit contents size SetNextWindowContentSize() ahead of time to prevent one-frame readjustment of scrolling. + // - (2) Items needs to have persistent ID Stack identifier = ID needs to not depends on their index. PushID(index) = KO. PushID(item_id) = OK. This is in order to focus items reliably after a selection. + // - (3) BeginXXXX process + // - (4) Focus process + // - (5) EndXXXX process + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (with deletion)"); + if (ImGui::TreeNode("Multi-Select (with deletion)")) + { + // Storing items data separately from selection data. + // (you may decide to store selection data inside your item (aka intrusive storage) if you don't need multiple views over same items) + // Use a custom selection.Adapter: store item identifier in Selection (instead of index) + static ImVector items; + static ExampleSelectionWithDeletion selection; + selection.UserData = (void*)&items; + selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { ImVector* p_items = (ImVector*)self->UserData; return (*p_items)[idx]; }; // Index -> ID + + ImGui::Text("Added features:"); + ImGui::BulletText("Dynamic list with Delete key support."); + ImGui::Text("Selection size: %d/%d", selection.Size, items.Size); + + // Initialize default list with 50 items + button to add/remove items. + static ImGuiID items_next_id = 0; + if (items_next_id == 0) + for (ImGuiID n = 0; n < 50; n++) + items.push_back(items_next_id++); + if (ImGui::SmallButton("Add 20 items")) { for (int n = 0; n < 20; n++) { items.push_back(items_next_id++); } } + ImGui::SameLine(); + if (ImGui::SmallButton("Remove 20 items")) { for (int n = IM_MIN(20, items.Size); n > 0; n--) { selection.SetItemSelected(items.back(), false); items.pop_back(); } } + + // (1) Extra to support deletion: Submit scrolling range to avoid glitches on deletion + const float items_height = ImGui::GetTextLineHeightWithSpacing(); + ImGui::SetNextWindowContentSize(ImVec2(0.0f, items.Size * items_height)); + + if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) + { + ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size); + selection.ApplyRequests(ms_io); + + const bool want_delete = ImGui::Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && (selection.Size > 0); + const int item_curr_idx_to_focus = want_delete ? selection.ApplyDeletionPreLoop(ms_io, items.Size) : -1; + + for (int n = 0; n < items.Size; n++) + { + const ImGuiID item_id = items[n]; + char label[64]; + sprintf(label, "Object %05u: %s", item_id, ExampleNames[item_id % IM_ARRAYSIZE(ExampleNames)]); + + bool item_is_selected = selection.Contains(item_id); + ImGui::SetNextItemSelectionUserData(n); + ImGui::Selectable(label, item_is_selected); + if (item_curr_idx_to_focus == n) + ImGui::SetKeyboardFocusHere(-1); + } + + // Apply multi-select requests + ms_io = ImGui::EndMultiSelect(); + selection.ApplyRequests(ms_io); + if (want_delete) + selection.ApplyDeletionPostLoop(ms_io, items, item_curr_idx_to_focus); + } + ImGui::EndChild(); + ImGui::TreePop(); + } + + // Implement a Dual List Box (#6648) + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (dual list box)"); + if (ImGui::TreeNode("Multi-Select (dual list box)")) + { + // Init default state + static ExampleDualListBox dlb; + if (dlb.Items[0].Size == 0 && dlb.Items[1].Size == 0) + for (int item_id = 0; item_id < IM_ARRAYSIZE(ExampleNames); item_id++) + dlb.Items[0].push_back((ImGuiID)item_id); + + // Show + dlb.Show(); + + ImGui::TreePop(); + } + + // Demonstrate using the clipper with BeginMultiSelect()/EndMultiSelect() + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (in a table)"); + if (ImGui::TreeNode("Multi-Select (in a table)")) + { + static ImGuiSelectionBasicStorage selection; + + const int ITEMS_COUNT = 10000; + ImGui::Text("Selection: %d/%d", selection.Size, ITEMS_COUNT); + if (ImGui::BeginTable("##Basket", 2, ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter)) + { + ImGui::TableSetupColumn("Object"); + ImGui::TableSetupColumn("Action"); + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableHeadersRow(); + + ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, ITEMS_COUNT); + selection.ApplyRequests(ms_io); + + ImGuiListClipper clipper; + clipper.Begin(ITEMS_COUNT); + if (ms_io->RangeSrcItem != -1) + clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped. + while (clipper.Step()) + { + for (int n = clipper.DisplayStart; n < clipper.DisplayEnd; n++) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + char label[64]; + sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]); + bool item_is_selected = selection.Contains((ImGuiID)n); + ImGui::SetNextItemSelectionUserData(n); + ImGui::Selectable(label, item_is_selected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap); + ImGui::TableNextColumn(); + ImGui::SmallButton("hello"); + } + } + + ms_io = ImGui::EndMultiSelect(); + selection.ApplyRequests(ms_io); + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (checkboxes)"); + if (ImGui::TreeNode("Multi-Select (checkboxes)")) + { + ImGui::Text("In a list of checkboxes (not selectable):"); + ImGui::BulletText("Using _NoAutoSelect + _NoAutoClear flags."); + ImGui::BulletText("Shift+Click to check multiple boxes."); + ImGui::BulletText("Shift+Keyboard to copy current value to other boxes."); + + // If you have an array of checkboxes, you may want to use NoAutoSelect + NoAutoClear and the ImGuiSelectionExternalStorage helper. + static bool items[20] = {}; + static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_NoAutoSelect | ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_ClearOnEscape; + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoAutoSelect", &flags, ImGuiMultiSelectFlags_NoAutoSelect); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoAutoClear", &flags, ImGuiMultiSelectFlags_NoAutoClear); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelect2d", &flags, ImGuiMultiSelectFlags_BoxSelect2d); // Cannot use ImGuiMultiSelectFlags_BoxSelect1d as checkboxes are varying width. + + if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY)) + { + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, -1, IM_ARRAYSIZE(items)); + ImGuiSelectionExternalStorage storage_wrapper; + storage_wrapper.UserData = (void*)items; + storage_wrapper.AdapterSetItemSelected = [](ImGuiSelectionExternalStorage* self, int n, bool selected) { bool* array = (bool*)self->UserData; array[n] = selected; }; + storage_wrapper.ApplyRequests(ms_io); + for (int n = 0; n < 20; n++) + { + char label[32]; + sprintf(label, "Item %d", n); + ImGui::SetNextItemSelectionUserData(n); + ImGui::Checkbox(label, &items[n]); + } + ms_io = ImGui::EndMultiSelect(); + storage_wrapper.ApplyRequests(ms_io); + } + ImGui::EndChild(); + + ImGui::TreePop(); + } + + // Demonstrate individual selection scopes in same window + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (multiple scopes)"); + if (ImGui::TreeNode("Multi-Select (multiple scopes)")) + { + // Use default select: Pass index to SetNextItemSelectionUserData(), store index in Selection + const int SCOPES_COUNT = 3; + const int ITEMS_COUNT = 8; // Per scope + static ImGuiSelectionBasicStorage selections_data[SCOPES_COUNT]; + + // Use ImGuiMultiSelectFlags_ScopeRect to not affect other selections in same window. + static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ScopeRect | ImGuiMultiSelectFlags_ClearOnEscape;// | ImGuiMultiSelectFlags_ClearOnClickVoid; + if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ScopeWindow", &flags, ImGuiMultiSelectFlags_ScopeWindow) && (flags & ImGuiMultiSelectFlags_ScopeWindow)) + flags &= ~ImGuiMultiSelectFlags_ScopeRect; + if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ScopeRect", &flags, ImGuiMultiSelectFlags_ScopeRect) && (flags & ImGuiMultiSelectFlags_ScopeRect)) + flags &= ~ImGuiMultiSelectFlags_ScopeWindow; + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ClearOnClickVoid", &flags, ImGuiMultiSelectFlags_ClearOnClickVoid); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelect1d", &flags, ImGuiMultiSelectFlags_BoxSelect1d); + + for (int selection_scope_n = 0; selection_scope_n < SCOPES_COUNT; selection_scope_n++) + { + ImGui::PushID(selection_scope_n); + ImGuiSelectionBasicStorage* selection = &selections_data[selection_scope_n]; + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection->Size, ITEMS_COUNT); + selection->ApplyRequests(ms_io); + + ImGui::SeparatorText("Selection scope"); + ImGui::Text("Selection size: %d/%d", selection->Size, ITEMS_COUNT); + + for (int n = 0; n < ITEMS_COUNT; n++) + { + char label[64]; + sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]); + bool item_is_selected = selection->Contains((ImGuiID)n); + ImGui::SetNextItemSelectionUserData(n); + ImGui::Selectable(label, item_is_selected); + } + + // Apply multi-select requests + ms_io = ImGui::EndMultiSelect(); + selection->ApplyRequests(ms_io); + ImGui::PopID(); + } + ImGui::TreePop(); + } + + // See ShowExampleAppAssetsBrowser() + if (ImGui::TreeNode("Multi-Select (tiled assets browser)")) + { + ImGui::Checkbox("Assets Browser", &demo_data->ShowAppAssetsBrowser); + ImGui::Text("(also access from 'Examples->Assets Browser' in menu)"); + ImGui::TreePop(); + } + + // Demonstrate supporting multiple-selection in a tree. + // - We don't use linear indices for selection user data, but our ExampleTreeNode* pointer directly! + // This showcase how SetNextItemSelectionUserData() never assume indices! + // - The difficulty here is to "interpolate" from RangeSrcItem to RangeDstItem in the SetAll/SetRange request. + // We want this interpolation to match what the user sees: in visible order, skipping closed nodes. + // This is implemented by our TreeGetNextNodeInVisibleOrder() user-space helper. + // - Important: In a real codebase aiming to implement full-featured selectable tree with custom filtering, you + // are more likely to build an array mapping sequential indices to visible tree nodes, since your + // filtering/search + clipping process will benefit from it. Having this will make this interpolation much easier. + // - Consider this a prototype: we are working toward simplifying some of it. + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (trees)"); + if (ImGui::TreeNode("Multi-Select (trees)")) + { + HelpMarker( + "This is rather advanced and experimental. If you are getting started with multi-select," + "please don't start by looking at how to use it for a tree!\n\n" + "Future versions will try to simplify and formalize some of this."); + + struct ExampleTreeFuncs + { + static void DrawNode(ExampleTreeNode* node, ImGuiSelectionBasicStorage* selection) + { + ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; + tree_node_flags |= ImGuiTreeNodeFlags_NavLeftJumpsBackHere; // Enable pressing left to jump to parent + if (node->Childs.Size == 0) + tree_node_flags |= ImGuiTreeNodeFlags_Bullet | ImGuiTreeNodeFlags_Leaf; + if (selection->Contains((ImGuiID)node->UID)) + tree_node_flags |= ImGuiTreeNodeFlags_Selected; + + // Using SetNextItemStorageID() to specify storage id, so we can easily peek into + // the storage holding open/close stage, using our TreeNodeGetOpen/TreeNodeSetOpen() functions. + ImGui::SetNextItemSelectionUserData((ImGuiSelectionUserData)(intptr_t)node); + ImGui::SetNextItemStorageID((ImGuiID)node->UID); + if (ImGui::TreeNodeEx(node->Name, tree_node_flags)) + { + for (ExampleTreeNode* child : node->Childs) + DrawNode(child, selection); + ImGui::TreePop(); + } + else if (ImGui::IsItemToggledOpen()) + { + TreeCloseAndUnselectChildNodes(node, selection); + } + } + + static bool TreeNodeGetOpen(ExampleTreeNode* node) + { + return ImGui::GetStateStorage()->GetBool((ImGuiID)node->UID); + } + + static void TreeNodeSetOpen(ExampleTreeNode* node, bool open) + { + ImGui::GetStateStorage()->SetBool((ImGuiID)node->UID, open); + } + + // When closing a node: 1) close and unselect all child nodes, 2) select parent if any child was selected. + // FIXME: This is currently handled by user logic but I'm hoping to eventually provide tree node + // features to do this automatically, e.g. a ImGuiTreeNodeFlags_AutoCloseChildNodes etc. + static int TreeCloseAndUnselectChildNodes(ExampleTreeNode* node, ImGuiSelectionBasicStorage* selection, int depth = 0) + { + // Recursive close (the test for depth == 0 is because we call this on a node that was just closed!) + int unselected_count = selection->Contains((ImGuiID)node->UID) ? 1 : 0; + if (depth == 0 || TreeNodeGetOpen(node)) + { + for (ExampleTreeNode* child : node->Childs) + unselected_count += TreeCloseAndUnselectChildNodes(child, selection, depth + 1); + TreeNodeSetOpen(node, false); + } + + // Select root node if any of its child was selected, otherwise unselect + selection->SetItemSelected((ImGuiID)node->UID, (depth == 0 && unselected_count > 0)); + return unselected_count; + } + + // Apply multi-selection requests + static void ApplySelectionRequests(ImGuiMultiSelectIO* ms_io, ExampleTreeNode* tree, ImGuiSelectionBasicStorage* selection) + { + for (ImGuiSelectionRequest& req : ms_io->Requests) + { + if (req.Type == ImGuiSelectionRequestType_SetAll) + { + if (req.Selected) + TreeSetAllInOpenNodes(tree, selection, req.Selected); + else + selection->Clear(); + } + else if (req.Type == ImGuiSelectionRequestType_SetRange) + { + ExampleTreeNode* first_node = (ExampleTreeNode*)(intptr_t)req.RangeFirstItem; + ExampleTreeNode* last_node = (ExampleTreeNode*)(intptr_t)req.RangeLastItem; + for (ExampleTreeNode* node = first_node; node != NULL; node = TreeGetNextNodeInVisibleOrder(node, last_node)) + selection->SetItemSelected((ImGuiID)node->UID, req.Selected); + } + } + } + + static void TreeSetAllInOpenNodes(ExampleTreeNode* node, ImGuiSelectionBasicStorage* selection, bool selected) + { + if (node->Parent != NULL) // Root node isn't visible nor selectable in our scheme + selection->SetItemSelected((ImGuiID)node->UID, selected); + if (node->Parent == NULL || TreeNodeGetOpen(node)) + for (ExampleTreeNode* child : node->Childs) + TreeSetAllInOpenNodes(child, selection, selected); + } + + // Interpolate in *user-visible order* AND only *over opened nodes*. + // If you have a sequential mapping tables (e.g. generated after a filter/search pass) this would be simpler. + // Here the tricks are that: + // - we store/maintain ExampleTreeNode::IndexInParent which allows implementing a linear iterator easily, without searches, without recursion. + // this could be replaced by a search in parent, aka 'int index_in_parent = curr_node->Parent->Childs.find_index(curr_node)' + // which would only be called when crossing from child to a parent, aka not too much. + // - we call SetNextItemStorageID() before our TreeNode() calls with an ID which doesn't relate to UI stack, + // making it easier to call TreeNodeGetOpen()/TreeNodeSetOpen() from any location. + static ExampleTreeNode* TreeGetNextNodeInVisibleOrder(ExampleTreeNode* curr_node, ExampleTreeNode* last_node) + { + // Reached last node + if (curr_node == last_node) + return NULL; + + // Recurse into childs. Query storage to tell if the node is open. + if (curr_node->Childs.Size > 0 && TreeNodeGetOpen(curr_node)) + return curr_node->Childs[0]; + + // Next sibling, then into our own parent + while (curr_node->Parent != NULL) + { + if (curr_node->IndexInParent + 1 < curr_node->Parent->Childs.Size) + return curr_node->Parent->Childs[curr_node->IndexInParent + 1]; + curr_node = curr_node->Parent; + } + return NULL; + } + + }; // ExampleTreeFuncs + + static ImGuiSelectionBasicStorage selection; + if (demo_data->DemoTree == NULL) + demo_data->DemoTree = ExampleTree_CreateDemoTree(); // Create tree once + ImGui::Text("Selection size: %d", selection.Size); + + if (ImGui::BeginChild("##Tree", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) + { + ExampleTreeNode* tree = demo_data->DemoTree; + ImGuiMultiSelectFlags ms_flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect2d; + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(ms_flags, selection.Size, -1); + ExampleTreeFuncs::ApplySelectionRequests(ms_io, tree, &selection); + for (ExampleTreeNode* node : tree->Childs) + ExampleTreeFuncs::DrawNode(node, &selection); + ms_io = ImGui::EndMultiSelect(); + ExampleTreeFuncs::ApplySelectionRequests(ms_io, tree, &selection); + } + ImGui::EndChild(); + + ImGui::TreePop(); + } + + // Advanced demonstration of BeginMultiSelect() + // - Showcase clipping. + // - Showcase deletion. + // - Showcase basic drag and drop. + // - Showcase TreeNode variant (note that tree node don't expand in the demo: supporting expanding tree nodes + clipping a separate thing). + // - Showcase using inside a table. + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (advanced)"); + //ImGui::SetNextItemOpen(true, ImGuiCond_Once); + if (ImGui::TreeNode("Multi-Select (advanced)")) + { + // Options + enum WidgetType { WidgetType_Selectable, WidgetType_TreeNode }; + static bool use_clipper = true; + static bool use_deletion = true; + static bool use_drag_drop = true; + static bool show_in_table = false; + static bool show_color_button = true; + static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; + static WidgetType widget_type = WidgetType_Selectable; + + if (ImGui::TreeNode("Options")) + { + if (ImGui::RadioButton("Selectables", widget_type == WidgetType_Selectable)) { widget_type = WidgetType_Selectable; } + ImGui::SameLine(); + if (ImGui::RadioButton("Tree nodes", widget_type == WidgetType_TreeNode)) { widget_type = WidgetType_TreeNode; } + ImGui::SameLine(); + HelpMarker("TreeNode() is technically supported but... using this correctly is more complicated (you need some sort of linear/random access to your tree, which is suited to advanced trees setups already implementing filters and clipper. We will work toward simplifying and demoing this.\n\nFor now the tree demo is actually a little bit meaningless because it is an empty tree with only root nodes."); + ImGui::Checkbox("Enable clipper", &use_clipper); + ImGui::Checkbox("Enable deletion", &use_deletion); + ImGui::Checkbox("Enable drag & drop", &use_drag_drop); + ImGui::Checkbox("Show in a table", &show_in_table); + ImGui::Checkbox("Show color button", &show_color_button); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SingleSelect", &flags, ImGuiMultiSelectFlags_SingleSelect); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoSelectAll", &flags, ImGuiMultiSelectFlags_NoSelectAll); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoRangeSelect", &flags, ImGuiMultiSelectFlags_NoRangeSelect); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoAutoSelect", &flags, ImGuiMultiSelectFlags_NoAutoSelect); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoAutoClear", &flags, ImGuiMultiSelectFlags_NoAutoClear); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoAutoClearOnReselect", &flags, ImGuiMultiSelectFlags_NoAutoClearOnReselect); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelect1d", &flags, ImGuiMultiSelectFlags_BoxSelect1d); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelect2d", &flags, ImGuiMultiSelectFlags_BoxSelect2d); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelectNoScroll", &flags, ImGuiMultiSelectFlags_BoxSelectNoScroll); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ClearOnEscape", &flags, ImGuiMultiSelectFlags_ClearOnEscape); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ClearOnClickVoid", &flags, ImGuiMultiSelectFlags_ClearOnClickVoid); + if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ScopeWindow", &flags, ImGuiMultiSelectFlags_ScopeWindow) && (flags & ImGuiMultiSelectFlags_ScopeWindow)) + flags &= ~ImGuiMultiSelectFlags_ScopeRect; + if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ScopeRect", &flags, ImGuiMultiSelectFlags_ScopeRect) && (flags & ImGuiMultiSelectFlags_ScopeRect)) + flags &= ~ImGuiMultiSelectFlags_ScopeWindow; + if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnClick", &flags, ImGuiMultiSelectFlags_SelectOnClick) && (flags & ImGuiMultiSelectFlags_SelectOnClick)) + flags &= ~ImGuiMultiSelectFlags_SelectOnClickRelease; + if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnClickRelease", &flags, ImGuiMultiSelectFlags_SelectOnClickRelease) && (flags & ImGuiMultiSelectFlags_SelectOnClickRelease)) + flags &= ~ImGuiMultiSelectFlags_SelectOnClick; + ImGui::SameLine(); HelpMarker("Allow dragging an unselected item without altering selection."); + ImGui::TreePop(); + } + + // Initialize default list with 1000 items. + // Use default selection.Adapter: Pass index to SetNextItemSelectionUserData(), store index in Selection + static ImVector items; + static int items_next_id = 0; + if (items_next_id == 0) { for (int n = 0; n < 1000; n++) { items.push_back(items_next_id++); } } + static ExampleSelectionWithDeletion selection; + static bool request_deletion_from_menu = false; // Queue deletion triggered from context menu + + ImGui::Text("Selection size: %d/%d", selection.Size, items.Size); + + const float items_height = (widget_type == WidgetType_TreeNode) ? ImGui::GetTextLineHeight() : ImGui::GetTextLineHeightWithSpacing(); + ImGui::SetNextWindowContentSize(ImVec2(0.0f, items.Size * items_height)); + if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) + { + ImVec2 color_button_sz(ImGui::GetFontSize(), ImGui::GetFontSize()); + if (widget_type == WidgetType_TreeNode) + ImGui::PushStyleVarY(ImGuiStyleVar_ItemSpacing, 0.0f); + + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size); + selection.ApplyRequests(ms_io); + + const bool want_delete = (ImGui::Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && (selection.Size > 0)) || request_deletion_from_menu; + const int item_curr_idx_to_focus = want_delete ? selection.ApplyDeletionPreLoop(ms_io, items.Size) : -1; + request_deletion_from_menu = false; + + if (show_in_table) + { + if (widget_type == WidgetType_TreeNode) + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0.0f, 0.0f)); + ImGui::BeginTable("##Split", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_NoPadOuterX); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.70f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.30f); + //ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacingY, 0.0f); + } + + ImGuiListClipper clipper; + if (use_clipper) + { + clipper.Begin(items.Size); + if (item_curr_idx_to_focus != -1) + clipper.IncludeItemByIndex(item_curr_idx_to_focus); // Ensure focused item is not clipped. + if (ms_io->RangeSrcItem != -1) + clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped. + } + + while (!use_clipper || clipper.Step()) + { + const int item_begin = use_clipper ? clipper.DisplayStart : 0; + const int item_end = use_clipper ? clipper.DisplayEnd : items.Size; + for (int n = item_begin; n < item_end; n++) + { + if (show_in_table) + ImGui::TableNextColumn(); + + const int item_id = items[n]; + const char* item_category = ExampleNames[item_id % IM_ARRAYSIZE(ExampleNames)]; + char label[64]; + sprintf(label, "Object %05d: %s", item_id, item_category); + + // IMPORTANT: for deletion refocus to work we need object ID to be stable, + // aka not depend on their index in the list. Here we use our persistent item_id + // instead of index to build a unique ID that will persist. + // (If we used PushID(index) instead, focus wouldn't be restored correctly after deletion). + ImGui::PushID(item_id); + + // Emit a color button, to test that Shift+LeftArrow landing on an item that is not part + // of the selection scope doesn't erroneously alter our selection. + if (show_color_button) + { + ImU32 dummy_col = (ImU32)((unsigned int)n * 0xC250B74B) | IM_COL32_A_MASK; + ImGui::ColorButton("##", ImColor(dummy_col), ImGuiColorEditFlags_NoTooltip, color_button_sz); + ImGui::SameLine(); + } + + // Submit item + bool item_is_selected = selection.Contains((ImGuiID)n); + bool item_is_open = false; + ImGui::SetNextItemSelectionUserData(n); + if (widget_type == WidgetType_Selectable) + { + ImGui::Selectable(label, item_is_selected, ImGuiSelectableFlags_None); + } + else if (widget_type == WidgetType_TreeNode) + { + ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; + if (item_is_selected) + tree_node_flags |= ImGuiTreeNodeFlags_Selected; + item_is_open = ImGui::TreeNodeEx(label, tree_node_flags); + } + + // Focus (for after deletion) + if (item_curr_idx_to_focus == n) + ImGui::SetKeyboardFocusHere(-1); + + // Drag and Drop + if (use_drag_drop && ImGui::BeginDragDropSource()) + { + // Create payload with full selection OR single unselected item. + // (the later is only possible when using ImGuiMultiSelectFlags_SelectOnClickRelease) + if (ImGui::GetDragDropPayload() == NULL) + { + ImVector payload_items; + void* it = NULL; + ImGuiID id = 0; + if (!item_is_selected) + payload_items.push_back(item_id); + else + while (selection.GetNextSelectedItem(&it, &id)) + payload_items.push_back((int)id); + ImGui::SetDragDropPayload("MULTISELECT_DEMO_ITEMS", payload_items.Data, (size_t)payload_items.size_in_bytes()); + } + + // Display payload content in tooltip + const ImGuiPayload* payload = ImGui::GetDragDropPayload(); + const int* payload_items = (int*)payload->Data; + const int payload_count = (int)payload->DataSize / (int)sizeof(int); + if (payload_count == 1) + ImGui::Text("Object %05d: %s", payload_items[0], ExampleNames[payload_items[0] % IM_ARRAYSIZE(ExampleNames)]); + else + ImGui::Text("Dragging %d objects", payload_count); + + ImGui::EndDragDropSource(); + } + + if (widget_type == WidgetType_TreeNode && item_is_open) + ImGui::TreePop(); + + // Right-click: context menu + if (ImGui::BeginPopupContextItem()) + { + ImGui::BeginDisabled(!use_deletion || selection.Size == 0); + sprintf(label, "Delete %d item(s)###DeleteSelected", selection.Size); + if (ImGui::Selectable(label)) + request_deletion_from_menu = true; + ImGui::EndDisabled(); + ImGui::Selectable("Close"); + ImGui::EndPopup(); + } + + // Demo content within a table + if (show_in_table) + { + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::InputText("###NoLabel", (char*)(void*)item_category, strlen(item_category), ImGuiInputTextFlags_ReadOnly); + ImGui::PopStyleVar(); + } + + ImGui::PopID(); + } + if (!use_clipper) + break; + } + + if (show_in_table) + { + ImGui::EndTable(); + if (widget_type == WidgetType_TreeNode) + ImGui::PopStyleVar(); + } + + // Apply multi-select requests + ms_io = ImGui::EndMultiSelect(); + selection.ApplyRequests(ms_io); + if (want_delete) + selection.ApplyDeletionPostLoop(ms_io, items, item_curr_idx_to_focus); + + if (widget_type == WidgetType_TreeNode) + ImGui::PopStyleVar(); + } + ImGui::EndChild(); + ImGui::TreePop(); + } + ImGui::TreePop(); + } +} + +//----------------------------------------------------------------------------- +// [SECTION] ShowDemoWindowLayout() +//----------------------------------------------------------------------------- + +static void ShowDemoWindowLayout() +{ + IMGUI_DEMO_MARKER("Layout"); + if (!ImGui::CollapsingHeader("Layout & Scrolling")) + return; + + IMGUI_DEMO_MARKER("Layout/Child windows"); + if (ImGui::TreeNode("Child windows")) + { + ImGui::SeparatorText("Child windows"); + + HelpMarker("Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window."); + static bool disable_mouse_wheel = false; + static bool disable_menu = false; + ImGui::Checkbox("Disable Mouse Wheel", &disable_mouse_wheel); + ImGui::Checkbox("Disable Menu", &disable_menu); + + // Child 1: no border, enable horizontal scrollbar + { + ImGuiWindowFlags window_flags = ImGuiWindowFlags_HorizontalScrollbar; + if (disable_mouse_wheel) + window_flags |= ImGuiWindowFlags_NoScrollWithMouse; + ImGui::BeginChild("ChildL", ImVec2(ImGui::GetContentRegionAvail().x * 0.5f, 260), ImGuiChildFlags_None, window_flags); + for (int i = 0; i < 100; i++) + ImGui::Text("%04d: scrollable region", i); + ImGui::EndChild(); + } + + ImGui::SameLine(); + + // Child 2: rounded border + { + ImGuiWindowFlags window_flags = ImGuiWindowFlags_None; + if (disable_mouse_wheel) + window_flags |= ImGuiWindowFlags_NoScrollWithMouse; + if (!disable_menu) + window_flags |= ImGuiWindowFlags_MenuBar; + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f); + ImGui::BeginChild("ChildR", ImVec2(0, 260), ImGuiChildFlags_Borders, window_flags); + if (!disable_menu && ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Menu")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + if (ImGui::BeginTable("split", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings)) { for (int i = 0; i < 100; i++) { @@ -2834,8 +4065,11 @@ static void ShowDemoWindowLayout() ImGui::SeparatorText("Manual-resize"); { HelpMarker("Drag bottom border to resize. Double-click bottom border to auto-fit to vertical contents."); + //if (ImGui::Button("Set Height to 200")) + // ImGui::SetNextWindowSize(ImVec2(-FLT_MIN, 200.0f)); + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetStyleColorVec4(ImGuiCol_FrameBg)); - if (ImGui::BeginChild("ResizableChild", ImVec2(-FLT_MIN, ImGui::GetTextLineHeightWithSpacing() * 8), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeY)) + if (ImGui::BeginChild("ResizableChild", ImVec2(-FLT_MIN, ImGui::GetTextLineHeightWithSpacing() * 8), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY)) for (int n = 0; n < 10; n++) ImGui::Text("Line %04d", n); ImGui::PopStyleColor(); @@ -2853,7 +4087,7 @@ static void ShowDemoWindowLayout() ImGui::DragInt("Max Height (in Lines)", &max_height_in_lines, 0.2f); ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 1), ImVec2(FLT_MAX, ImGui::GetTextLineHeightWithSpacing() * max_height_in_lines)); - if (ImGui::BeginChild("ConstrainedChild", ImVec2(-FLT_MIN, 0.0f), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) + if (ImGui::BeginChild("ConstrainedChild", ImVec2(-FLT_MIN, 0.0f), ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeY)) for (int n = 0; n < draw_lines; n++) ImGui::Text("Line %04d", n); ImGui::EndChild(); @@ -2871,11 +4105,11 @@ static void ShowDemoWindowLayout() { static int offset_x = 0; static bool override_bg_color = true; - static ImGuiChildFlags child_flags = ImGuiChildFlags_Border | ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY; + static ImGuiChildFlags child_flags = ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY; ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); ImGui::DragInt("Offset X", &offset_x, 1.0f, -1000, 1000); ImGui::Checkbox("Override ChildBg color", &override_bg_color); - ImGui::CheckboxFlags("ImGuiChildFlags_Border", &child_flags, ImGuiChildFlags_Border); + ImGui::CheckboxFlags("ImGuiChildFlags_Borders", &child_flags, ImGuiChildFlags_Borders); ImGui::CheckboxFlags("ImGuiChildFlags_AlwaysUseWindowPadding", &child_flags, ImGuiChildFlags_AlwaysUseWindowPadding); ImGui::CheckboxFlags("ImGuiChildFlags_ResizeX", &child_flags, ImGuiChildFlags_ResizeX); ImGui::CheckboxFlags("ImGuiChildFlags_ResizeY", &child_flags, ImGuiChildFlags_ResizeY); @@ -3061,7 +4295,7 @@ static void ShowDemoWindowLayout() ImGui::Text("Manual wrapping:"); ImGuiStyle& style = ImGui::GetStyle(); int buttons_count = 20; - float window_visible_x2 = ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x; + float window_visible_x2 = ImGui::GetCursorScreenPos().x + ImGui::GetContentRegionAvail().x; for (int n = 0; n < buttons_count; n++) { ImGui::PushID(n); @@ -3142,7 +4376,7 @@ static void ShowDemoWindowLayout() // down by FramePadding.y ahead of time) ImGui::AlignTextToFramePadding(); ImGui::Text("OK Blahblah"); ImGui::SameLine(); - ImGui::Button("Some framed item"); ImGui::SameLine(); + ImGui::Button("Some framed item##2"); ImGui::SameLine(); HelpMarker("We call AlignTextToFramePadding() to vertically align the text baseline by +FramePadding.y"); // SmallButton() uses the same vertical padding as Text @@ -3285,7 +4519,7 @@ static void ShowDemoWindowLayout() const ImGuiWindowFlags child_flags = enable_extra_decorations ? ImGuiWindowFlags_MenuBar : 0; const ImGuiID child_id = ImGui::GetID((void*)(intptr_t)i); - const bool child_is_visible = ImGui::BeginChild(child_id, ImVec2(child_w, 200.0f), ImGuiChildFlags_Border, child_flags); + const bool child_is_visible = ImGui::BeginChild(child_id, ImVec2(child_w, 200.0f), ImGuiChildFlags_Borders, child_flags); if (ImGui::BeginMenuBar()) { ImGui::TextUnformatted("abc"); @@ -3332,7 +4566,7 @@ static void ShowDemoWindowLayout() float child_height = ImGui::GetTextLineHeight() + style.ScrollbarSize + style.WindowPadding.y * 2.0f; ImGuiWindowFlags child_flags = ImGuiWindowFlags_HorizontalScrollbar | (enable_extra_decorations ? ImGuiWindowFlags_AlwaysVerticalScrollbar : 0); ImGuiID child_id = ImGui::GetID((void*)(intptr_t)i); - bool child_is_visible = ImGui::BeginChild(child_id, ImVec2(-100, child_height), ImGuiChildFlags_Border, child_flags); + bool child_is_visible = ImGui::BeginChild(child_id, ImVec2(-100, child_height), ImGuiChildFlags_Borders, child_flags); if (scroll_to_off) ImGui::SetScrollX(scroll_to_off_px); if (scroll_to_pos) @@ -3374,7 +4608,7 @@ static void ShowDemoWindowLayout() ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2.0f, 1.0f)); ImVec2 scrolling_child_size = ImVec2(0, ImGui::GetFrameHeightWithSpacing() * 7 + 30); - ImGui::BeginChild("scrolling", scrolling_child_size, ImGuiChildFlags_Border, ImGuiWindowFlags_HorizontalScrollbar); + ImGui::BeginChild("scrolling", scrolling_child_size, ImGuiChildFlags_Borders, ImGuiWindowFlags_HorizontalScrollbar); for (int line = 0; line < lines; line++) { // Display random stuff. For the sake of this trivial demo we are using basic Button() + SameLine() @@ -3520,7 +4754,7 @@ static void ShowDemoWindowLayout() } if (show_child) { - ImGui::BeginChild("child", ImVec2(0, 0), ImGuiChildFlags_Border); + ImGui::BeginChild("child", ImVec2(0, 0), ImGuiChildFlags_Borders); ImGui::EndChild(); } ImGui::End(); @@ -3529,8 +4763,8 @@ static void ShowDemoWindowLayout() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Layout/Clipping"); - if (ImGui::TreeNode("Clipping")) + IMGUI_DEMO_MARKER("Layout/Text Clipping"); + if (ImGui::TreeNode("Text Clipping")) { static ImVec2 size(100.0f, 100.0f); static ImVec2 offset(30.0f, 30.0f); @@ -3626,6 +4860,10 @@ static void ShowDemoWindowLayout() } } +//----------------------------------------------------------------------------- +// [SECTION] ShowDemoWindowPopups() +//----------------------------------------------------------------------------- + static void ShowDemoWindowPopups() { IMGUI_DEMO_MARKER("Popups"); @@ -4002,8 +5240,8 @@ const ImGuiTableSortSpecs* MyItem::s_current_sort_specs = NULL; static void PushStyleCompact() { ImGuiStyle& style = ImGui::GetStyle(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, (float)(int)(style.FramePadding.y * 0.60f))); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, (float)(int)(style.ItemSpacing.y * 0.60f))); + ImGui::PushStyleVarY(ImGuiStyleVar_FramePadding, (float)(int)(style.FramePadding.y * 0.60f)); + ImGui::PushStyleVarY(ImGuiStyleVar_ItemSpacing, (float)(int)(style.ItemSpacing.y * 0.60f)); } static void PopStyleCompact() @@ -4086,6 +5324,10 @@ static void ShowTableColumnsStatusFlags(ImGuiTableColumnFlags flags) ImGui::CheckboxFlags("_IsHovered", &flags, ImGuiTableColumnFlags_IsHovered); } +//----------------------------------------------------------------------------- +// [SECTION] ShowDemoWindowTables() +//----------------------------------------------------------------------------- + static void ShowDemoWindowTables() { //ImGui::SetNextItemOpen(true, ImGuiCond_Once); @@ -4204,7 +5446,7 @@ static void ShowDemoWindowTables() PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", &flags, ImGuiTableFlags_RowBg); ImGui::CheckboxFlags("ImGuiTableFlags_Borders", &flags, ImGuiTableFlags_Borders); - ImGui::SameLine(); HelpMarker("ImGuiTableFlags_Borders\n = ImGuiTableFlags_BordersInnerV\n | ImGuiTableFlags_BordersOuterV\n | ImGuiTableFlags_BordersInnerV\n | ImGuiTableFlags_BordersOuterH"); + ImGui::SameLine(); HelpMarker("ImGuiTableFlags_Borders\n = ImGuiTableFlags_BordersInnerV\n | ImGuiTableFlags_BordersOuterV\n | ImGuiTableFlags_BordersInnerH\n | ImGuiTableFlags_BordersOuterH"); ImGui::Indent(); ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", &flags, ImGuiTableFlags_BordersH); @@ -5033,7 +6275,7 @@ static void ShowDemoWindowTables() for (int row = 0; row < 8; row++) { if ((row % 3) == 2) - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(style.CellPadding.x, 20.0f)); + ImGui::PushStyleVarY(ImGuiStyleVar_CellPadding, 20.0f); ImGui::TableNextRow(ImGuiTableRowFlags_None); ImGui::TableNextColumn(); ImGui::Text("CellPadding.y = %.2f", style.CellPadding.y); @@ -5312,7 +6554,11 @@ static void ShowDemoWindowTables() // FIXME: It would be nice to actually demonstrate full-featured selection using those checkbox. static bool column_selected[3] = {}; - // Instead of calling TableHeadersRow() we'll submit custom headers ourselves + // Instead of calling TableHeadersRow() we'll submit custom headers ourselves. + // (A different approach is also possible: + // - Specify ImGuiTableColumnFlags_NoHeaderLabel in some TableSetupColumn() call. + // - Call TableHeadersRow() normally. This will submit TableHeader() with no name. + // - Then call TableSetColumnIndex() to position yourself in the column and submit your stuff e.g. Checkbox().) ImGui::TableNextRow(ImGuiTableRowFlags_Headers); for (int column = 0; column < COLUMNS_COUNT; column++) { @@ -5327,6 +6573,7 @@ static void ShowDemoWindowTables() ImGui::PopID(); } + // Submit table contents for (int row = 0; row < 5; row++) { ImGui::TableNextRow(); @@ -5361,6 +6608,7 @@ static void ShowDemoWindowTables() ImGui::CheckboxFlags("_ScrollX", &table_flags, ImGuiTableFlags_ScrollX); ImGui::CheckboxFlags("_ScrollY", &table_flags, ImGuiTableFlags_ScrollY); ImGui::CheckboxFlags("_Resizable", &table_flags, ImGuiTableFlags_Resizable); + ImGui::CheckboxFlags("_Sortable", &table_flags, ImGuiTableFlags_Sortable); ImGui::CheckboxFlags("_NoBordersInBody", &table_flags, ImGuiTableFlags_NoBordersInBody); ImGui::CheckboxFlags("_HighlightHoveredColumn", &table_flags, ImGuiTableFlags_HighlightHoveredColumn); ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); @@ -5858,7 +7106,6 @@ static void ShowDemoWindowTables() // Show data // FIXME-TABLE FIXME-NAV: How we can get decent up/down even though we have the buttons here? - ImGui::PushButtonRepeat(true); #if 1 // Demonstrate using clipper for large vertical lists ImGuiListClipper clipper; @@ -5943,7 +7190,6 @@ static void ShowDemoWindowTables() ImGui::PopID(); } } - ImGui::PopButtonRepeat(); // Store some info to display debug details below table_scroll_cur = ImVec2(ImGui::GetScrollX(), ImGui::GetScrollY()); @@ -6053,12 +7299,14 @@ static void ShowDemoWindowColumns() { if (h_borders && ImGui::GetColumnIndex() == 0) ImGui::Separator(); + ImGui::PushID(i); ImGui::Text("%c%c%c", 'a' + i, 'a' + i, 'a' + i); ImGui::Text("Width %.2f", ImGui::GetColumnWidth()); ImGui::Text("Avail %.2f", ImGui::GetContentRegionAvail().x); ImGui::Text("Offset %.2f", ImGui::GetColumnOffset()); ImGui::Text("Long text that is likely to clip"); ImGui::Button("Button", ImVec2(-FLT_MIN, 0.0f)); + ImGui::PopID(); ImGui::NextColumn(); } ImGui::Columns(1); @@ -6181,6 +7429,10 @@ static void ShowDemoWindowColumns() ImGui::TreePop(); } +//----------------------------------------------------------------------------- +// [SECTION] ShowDemoWindowInputs() +//----------------------------------------------------------------------------- + static void ShowDemoWindowInputs() { IMGUI_DEMO_MARKER("Inputs & Focus"); @@ -6212,13 +7464,8 @@ static void ShowDemoWindowInputs() // displaying the data for old/new backends. // User code should never have to go through such hoops! // You can generally iterate between ImGuiKey_NamedKey_BEGIN and ImGuiKey_NamedKey_END. -#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO struct funcs { static bool IsLegacyNativeDupe(ImGuiKey) { return false; } }; ImGuiKey start_key = ImGuiKey_NamedKey_BEGIN; -#else - struct funcs { static bool IsLegacyNativeDupe(ImGuiKey key) { return key >= 0 && key < 512 && ImGui::GetIO().KeyMap[key] != -1; } }; // Hide Native<>ImGuiKey duplicates when both exists in the array - ImGuiKey start_key = (ImGuiKey)0; -#endif ImGui::Text("Keys down:"); for (ImGuiKey key = start_key; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !ImGui::IsKeyDown(key)) continue; ImGui::SameLine(); ImGui::Text((key < ImGuiKey_NamedKey_BEGIN) ? "\"%s\"" : "\"%s\" %d", ImGui::GetKeyName(key), key); } ImGui::Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : ""); ImGui::Text("Chars queue:"); for (int i = 0; i < io.InputQueueCharacters.Size; i++) { ImWchar c = io.InputQueueCharacters[i]; ImGui::SameLine(); ImGui::Text("\'%c\' (0x%04X)", (c > ' ' && c <= 255) ? (char)c : '?', c); } // FIXME: We should convert 'c' to UTF-8 here but the functions are not public. @@ -6377,7 +7624,8 @@ static void ShowDemoWindowInputs() IM_ASSERT(IM_ARRAYSIZE(mouse_cursors_names) == ImGuiMouseCursor_COUNT); ImGuiMouseCursor current = ImGui::GetMouseCursor(); - ImGui::Text("Current mouse cursor = %d: %s", current, mouse_cursors_names[current]); + const char* cursor_name = (current >= ImGuiMouseCursor_Arrow) && (current < ImGuiMouseCursor_COUNT) ? mouse_cursors_names[current] : "N/A"; + ImGui::Text("Current mouse cursor = %d: %s", current, cursor_name); ImGui::BeginDisabled(true); ImGui::CheckboxFlags("io.BackendFlags: HasMouseCursors", &io.BackendFlags, ImGuiBackendFlags_HasMouseCursors); ImGui::EndDisabled(); @@ -6406,10 +7654,10 @@ static void ShowDemoWindowInputs() ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); ImGui::InputText("3", buf, IM_ARRAYSIZE(buf)); - ImGui::PushTabStop(false); + ImGui::PushItemFlag(ImGuiItemFlags_NoTabStop, true); ImGui::InputText("4 (tab skip)", buf, IM_ARRAYSIZE(buf)); ImGui::SameLine(); HelpMarker("Item won't be cycled through when using TAB or Shift+Tab."); - ImGui::PopTabStop(); + ImGui::PopItemFlag(); ImGui::InputText("5", buf, IM_ARRAYSIZE(buf)); ImGui::TreePop(); } @@ -6431,12 +7679,12 @@ static void ShowDemoWindowInputs() ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); if (ImGui::IsItemActive()) has_focus = 2; - ImGui::PushTabStop(false); + ImGui::PushItemFlag(ImGuiItemFlags_NoTabStop, true); if (focus_3) ImGui::SetKeyboardFocusHere(); ImGui::InputText("3 (tab skip)", buf, IM_ARRAYSIZE(buf)); if (ImGui::IsItemActive()) has_focus = 3; ImGui::SameLine(); HelpMarker("Item won't be cycled through when using TAB or Shift+Tab."); - ImGui::PopTabStop(); + ImGui::PopItemFlag(); if (has_focus) ImGui::Text("Item with focus: %d", has_focus); @@ -6501,6 +7749,17 @@ void ImGui::ShowAboutWindow(bool* p_open) } IMGUI_DEMO_MARKER("Tools/About Dear ImGui"); ImGui::Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); + + ImGui::TextLinkOpenURL("Homepage", "https://github.com/ocornut/imgui"); + ImGui::SameLine(); + ImGui::TextLinkOpenURL("FAQ", "https://github.com/ocornut/imgui/blob/master/docs/FAQ.md"); + ImGui::SameLine(); + ImGui::TextLinkOpenURL("Wiki", "https://github.com/ocornut/imgui/wiki"); + ImGui::SameLine(); + ImGui::TextLinkOpenURL("Releases", "https://github.com/ocornut/imgui/releases"); + ImGui::SameLine(); + ImGui::TextLinkOpenURL("Funding", "https://github.com/ocornut/imgui/wiki/Funding"); + ImGui::Separator(); ImGui::Text("By Omar Cornut and all Dear ImGui contributors."); ImGui::Text("Dear ImGui is licensed under the MIT License, see LICENSE for more information."); @@ -6529,9 +7788,6 @@ void ImGui::ShowAboutWindow(bool* p_open) #ifdef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImGui::Text("define: IMGUI_DISABLE_OBSOLETE_FUNCTIONS"); #endif -#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO - ImGui::Text("define: IMGUI_DISABLE_OBSOLETE_KEYIO"); -#endif #ifdef IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS ImGui::Text("define: IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS"); #endif @@ -6591,6 +7847,13 @@ void ImGui::ShowAboutWindow(bool* p_open) #endif #ifdef __EMSCRIPTEN__ ImGui::Text("define: __EMSCRIPTEN__"); + ImGui::Text("Emscripten: %d.%d.%d", __EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__); +#endif +#ifdef IMGUI_HAS_VIEWPORT + ImGui::Text("define: IMGUI_HAS_VIEWPORT"); +#endif +#ifdef IMGUI_HAS_DOCK + ImGui::Text("define: IMGUI_HAS_DOCK"); #endif ImGui::Separator(); ImGui::Text("io.BackendPlatformName: %s", io.BackendPlatformName ? io.BackendPlatformName : "NULL"); @@ -6598,12 +7861,25 @@ void ImGui::ShowAboutWindow(bool* p_open) ImGui::Text("io.ConfigFlags: 0x%08X", io.ConfigFlags); if (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) ImGui::Text(" NavEnableKeyboard"); if (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) ImGui::Text(" NavEnableGamepad"); - if (io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) ImGui::Text(" NavEnableSetMousePos"); - if (io.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard) ImGui::Text(" NavNoCaptureKeyboard"); if (io.ConfigFlags & ImGuiConfigFlags_NoMouse) ImGui::Text(" NoMouse"); if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) ImGui::Text(" NoMouseCursorChange"); + if (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard) ImGui::Text(" NoKeyboard"); + if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) ImGui::Text(" DockingEnable"); + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) ImGui::Text(" ViewportsEnable"); + if (io.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleViewports) ImGui::Text(" DpiEnableScaleViewports"); + if (io.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ImGui::Text(" DpiEnableScaleFonts"); if (io.MouseDrawCursor) ImGui::Text("io.MouseDrawCursor"); + if (io.ConfigViewportsNoAutoMerge) ImGui::Text("io.ConfigViewportsNoAutoMerge"); + if (io.ConfigViewportsNoTaskBarIcon) ImGui::Text("io.ConfigViewportsNoTaskBarIcon"); + if (io.ConfigViewportsNoDecoration) ImGui::Text("io.ConfigViewportsNoDecoration"); + if (io.ConfigViewportsNoDefaultParent) ImGui::Text("io.ConfigViewportsNoDefaultParent"); + if (io.ConfigDockingNoSplit) ImGui::Text("io.ConfigDockingNoSplit"); + if (io.ConfigDockingWithShift) ImGui::Text("io.ConfigDockingWithShift"); + if (io.ConfigDockingAlwaysTabBar) ImGui::Text("io.ConfigDockingAlwaysTabBar"); + if (io.ConfigDockingTransparentPayload) ImGui::Text("io.ConfigDockingTransparentPayload"); if (io.ConfigMacOSXBehaviors) ImGui::Text("io.ConfigMacOSXBehaviors"); + if (io.ConfigNavMoveSetMousePos) ImGui::Text("io.ConfigNavMoveSetMousePos"); + if (io.ConfigNavCaptureKeyboard) ImGui::Text("io.ConfigNavCaptureKeyboard"); if (io.ConfigInputTextCursorBlink) ImGui::Text("io.ConfigInputTextCursorBlink"); if (io.ConfigWindowsResizeFromEdges) ImGui::Text("io.ConfigWindowsResizeFromEdges"); if (io.ConfigWindowsMoveFromTitleBarOnly) ImGui::Text("io.ConfigWindowsMoveFromTitleBarOnly"); @@ -6612,7 +7888,10 @@ void ImGui::ShowAboutWindow(bool* p_open) if (io.BackendFlags & ImGuiBackendFlags_HasGamepad) ImGui::Text(" HasGamepad"); if (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) ImGui::Text(" HasMouseCursors"); if (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos) ImGui::Text(" HasSetMousePos"); + if (io.BackendFlags & ImGuiBackendFlags_PlatformHasViewports) ImGui::Text(" PlatformHasViewports"); + if (io.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport)ImGui::Text(" HasMouseHoveredViewport"); if (io.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) ImGui::Text(" RendererHasVtxOffset"); + if (io.BackendFlags & ImGuiBackendFlags_RendererHasViewports) ImGui::Text(" RendererHasViewports"); ImGui::Separator(); ImGui::Text("io.Fonts: %d fonts, Flags: 0x%08X, TexSize: %d,%d", io.Fonts->Fonts.Size, io.Fonts->Flags, io.Fonts->TexWidth, io.Fonts->TexHeight); ImGui::Text("io.DisplaySize: %.2f,%.2f", io.DisplaySize.x, io.DisplaySize.y); @@ -6756,6 +8035,8 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("TabBarBorderSize", &style.TabBarBorderSize, 0.0f, 2.0f, "%.0f"); + ImGui::SliderFloat("TabBarOverlineSize", &style.TabBarOverlineSize, 0.0f, 2.0f, "%.0f"); + ImGui::SameLine(); HelpMarker("Overline is only drawn over the selected tab when ImGuiTabBarFlags_DrawSelectedOverline is set."); ImGui::SeparatorText("Rounding"); ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f, "%.0f"); @@ -6786,6 +8067,9 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::SliderFloat2("SeparatorTextPadding", (float*)&style.SeparatorTextPadding, 0.0f, 40.0f, "%.0f"); ImGui::SliderFloat("LogSliderDeadzone", &style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f"); + ImGui::SeparatorText("Docking"); + ImGui::SliderFloat("DockingSplitterSize", &style.DockingSeparatorSize, 0.0f, 12.0f, "%.0f"); + ImGui::SeparatorText("Tooltips"); for (int n = 0; n < 2; n++) if (ImGui::TreeNodeEx(n == 0 ? "HoverFlagsForTooltipMouse" : "HoverFlagsForTooltipNav")) @@ -6843,7 +8127,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) "Right-click to open edit options menu."); ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 10), ImVec2(FLT_MAX, FLT_MAX)); - ImGui::BeginChild("##colors", ImVec2(0, 0), ImGuiChildFlags_Border, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NavFlattened); + ImGui::BeginChild("##colors", ImVec2(0, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar); ImGui::PushItemWidth(ImGui::GetFontSize() * -12); for (int i = 0; i < ImGuiCol_COUNT; i++) { @@ -6924,10 +8208,10 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos()); if (show_samples && ImGui::BeginTooltip()) { - ImGui::TextUnformatted("(R = radius, N = number of segments)"); + ImGui::TextUnformatted("(R = radius, N = approx number of segments)"); ImGui::Spacing(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); - const float min_widget_width = ImGui::CalcTextSize("N: MMM\nR: MMM").x; + const float min_widget_width = ImGui::CalcTextSize("R: MMM\nN: MMM").x; for (int n = 0; n < 8; n++) { const float RAD_MIN = 5.0f; @@ -6936,6 +8220,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::BeginGroup(); + // N is not always exact here due to how PathArcTo() function work internally ImGui::Text("R: %.f\nN: %d", rad, draw_list->_CalcCircleAutoSegmentCount(rad)); const float canvas_width = IM_MAX(min_widget_width, rad * 2.0f); @@ -7076,7 +8361,7 @@ static void ShowExampleMenuFile() { static bool enabled = true; ImGui::MenuItem("Enabled", "", &enabled); - ImGui::BeginChild("child", ImVec2(0, 60), ImGuiChildFlags_Border); + ImGui::BeginChild("child", ImVec2(0, 60), ImGuiChildFlags_Borders); for (int i = 0; i < 10; i++) ImGui::Text("Scrolling Text %d", i); ImGui::EndChild(); @@ -7243,7 +8528,7 @@ struct ExampleAppConsole // Reserve enough left-over height for 1 separator + 1 input text const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); - if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), ImGuiChildFlags_None, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NavFlattened)) + if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_HorizontalScrollbar)) { if (ImGui::BeginPopupContextWindow()) { @@ -7673,7 +8958,7 @@ static void ShowExampleAppLayout(bool* p_open) // Left static int selected = 0; { - ImGui::BeginChild("left pane", ImVec2(150, 0), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeX); + ImGui::BeginChild("left pane", ImVec2(150, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeX); for (int i = 0; i < 100; i++) { // FIXME: Good candidate to use ImGuiSelectableFlags_SelectOnNav @@ -7704,72 +8989,145 @@ static void ShowExampleAppLayout(bool* p_open) ImGui::Text("ID: 0123456789"); ImGui::EndTabItem(); } - ImGui::EndTabBar(); + ImGui::EndTabBar(); + } + ImGui::EndChild(); + if (ImGui::Button("Revert")) {} + ImGui::SameLine(); + if (ImGui::Button("Save")) {} + ImGui::EndGroup(); + } + } + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor() +//----------------------------------------------------------------------------- +// Some of the interactions are a bit lack-luster: +// - We would want pressing validating or leaving the filter to somehow restore focus. +// - We may want more advanced filtering (child nodes) and clipper support: both will need extra work. +// - We would want to customize some keyboard interactions to easily keyboard navigate between the tree and the properties. +//----------------------------------------------------------------------------- + +struct ExampleAppPropertyEditor +{ + ImGuiTextFilter Filter; + ExampleTreeNode* VisibleNode = NULL; + + void Draw(ExampleTreeNode* root_node) + { + // Left side: draw tree + // - Currently using a table to benefit from RowBg feature + if (ImGui::BeginChild("##tree", ImVec2(300, 0), ImGuiChildFlags_ResizeX | ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened)) + { + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip); + ImGui::PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true); + if (ImGui::InputTextWithHint("##Filter", "incl,-excl", Filter.InputBuf, IM_ARRAYSIZE(Filter.InputBuf), ImGuiInputTextFlags_EscapeClearsAll)) + Filter.Build(); + ImGui::PopItemFlag(); + + if (ImGui::BeginTable("##bg", 1, ImGuiTableFlags_RowBg)) + { + for (ExampleTreeNode* node : root_node->Childs) + if (Filter.PassFilter(node->Name)) // Filter root node + DrawTreeNode(node); + ImGui::EndTable(); + } + } + ImGui::EndChild(); + + // Right side: draw properties + ImGui::SameLine(); + + ImGui::BeginGroup(); // Lock X position + if (ExampleTreeNode* node = VisibleNode) + { + ImGui::Text("%s", node->Name); + ImGui::TextDisabled("UID: 0x%08X", node->UID); + ImGui::Separator(); + if (ImGui::BeginTable("##properties", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) + { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 2.0f); // Default twice larger + if (node->HasData) + { + // In a typical application, the structure description would be derived from a data-driven system. + // - We try to mimic this with our ExampleMemberInfo structure and the ExampleTreeNodeMemberInfos[] array. + // - Limits and some details are hard-coded to simplify the demo. + for (const ExampleMemberInfo& field_desc : ExampleTreeNodeMemberInfos) + { + ImGui::TableNextRow(); + ImGui::PushID(field_desc.Name); + ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(field_desc.Name); + ImGui::TableNextColumn(); + void* field_ptr = (void*)(((unsigned char*)node) + field_desc.Offset); + switch (field_desc.DataType) + { + case ImGuiDataType_Bool: + { + IM_ASSERT(field_desc.DataCount == 1); + ImGui::Checkbox("##Editor", (bool*)field_ptr); + break; + } + case ImGuiDataType_S32: + { + int v_min = INT_MIN, v_max = INT_MAX; + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::DragScalarN("##Editor", field_desc.DataType, field_ptr, field_desc.DataCount, 1.0f, &v_min, &v_max); + break; + } + case ImGuiDataType_Float: + { + float v_min = 0.0f, v_max = 1.0f; + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::SliderScalarN("##Editor", field_desc.DataType, field_ptr, field_desc.DataCount, &v_min, &v_max); + break; + } + } + ImGui::PopID(); + } + } + ImGui::EndTable(); } - ImGui::EndChild(); - if (ImGui::Button("Revert")) {} - ImGui::SameLine(); - if (ImGui::Button("Save")) {} - ImGui::EndGroup(); } + ImGui::EndGroup(); } - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor() -//----------------------------------------------------------------------------- - -static void ShowPlaceholderObject(const char* prefix, int uid) -{ - // Use object uid as identifier. Most commonly you could also use the object pointer as a base ID. - ImGui::PushID(uid); - - // Text and Tree nodes are less high than framed widgets, using AlignTextToFramePadding() we add vertical spacing to make the tree lines equal high. - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::AlignTextToFramePadding(); - bool node_open = ImGui::TreeNode("Object", "%s_%u", prefix, uid); - ImGui::TableSetColumnIndex(1); - ImGui::Text("my sailor is rich"); - if (node_open) - { - static float placeholder_members[8] = { 0.0f, 0.0f, 1.0f, 3.1416f, 100.0f, 999.0f }; - for (int i = 0; i < 8; i++) + void DrawTreeNode(ExampleTreeNode* node) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushID(node->UID); + ImGuiTreeNodeFlags tree_flags = ImGuiTreeNodeFlags_None; + tree_flags |= ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; // Standard opening mode as we are likely to want to add selection afterwards + tree_flags |= ImGuiTreeNodeFlags_NavLeftJumpsBackHere; // Left arrow support + if (node == VisibleNode) + tree_flags |= ImGuiTreeNodeFlags_Selected; + if (node->Childs.Size == 0) + tree_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet; + if (node->DataMyBool == false) + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]); + bool node_open = ImGui::TreeNodeEx("", tree_flags, "%s", node->Name); + if (node->DataMyBool == false) + ImGui::PopStyleColor(); + if (ImGui::IsItemFocused()) + VisibleNode = node; + if (node_open) { - ImGui::PushID(i); // Use field index as identifier. - if (i < 2) - { - ShowPlaceholderObject("Child", 424242); - } - else - { - // Here we use a TreeNode to highlight on hover (we could use e.g. Selectable as well) - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::AlignTextToFramePadding(); - ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_Bullet; - ImGui::TreeNodeEx("Field", flags, "Field_%d", i); - - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-FLT_MIN); - if (i >= 5) - ImGui::InputFloat("##value", &placeholder_members[i], 1.0f); - else - ImGui::DragFloat("##value", &placeholder_members[i], 0.01f); - ImGui::NextColumn(); - } - ImGui::PopID(); + for (ExampleTreeNode* child : node->Childs) + DrawTreeNode(child); + ImGui::TreePop(); } - ImGui::TreePop(); + ImGui::PopID(); } - ImGui::PopID(); -} +}; -// Demonstrate create a simple property editor. -// This demo is a bit lackluster nowadays, would be nice to improve. -static void ShowExampleAppPropertyEditor(bool* p_open) +// Demonstrate creating a simple property editor. +static void ShowExampleAppPropertyEditor(bool* p_open, ImGuiDemoWindowData* demo_data) { ImGui::SetNextWindowSize(ImVec2(430, 450), ImGuiCond_FirstUseEver); if (!ImGui::Begin("Example: Property editor", p_open)) @@ -7779,25 +9137,11 @@ static void ShowExampleAppPropertyEditor(bool* p_open) } IMGUI_DEMO_MARKER("Examples/Property Editor"); - HelpMarker( - "This example shows how you may implement a property editor using two columns.\n" - "All objects/fields data are dummies here.\n"); - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2)); - if (ImGui::BeginTable("##split", 2, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) - { - ImGui::TableSetupScrollFreeze(0, 1); - ImGui::TableSetupColumn("Object"); - ImGui::TableSetupColumn("Contents"); - ImGui::TableHeadersRow(); - - // Iterate placeholder objects (all the same data) - for (int obj_i = 0; obj_i < 4; obj_i++) - ShowPlaceholderObject("Object", obj_i); + static ExampleAppPropertyEditor property_editor; + if (demo_data->DemoTree == NULL) + demo_data->DemoTree = ExampleTree_CreateDemoTree(); + property_editor.Draw(demo_data->DemoTree); - ImGui::EndTable(); - } - ImGui::PopStyleVar(); ImGui::End(); } @@ -7972,6 +9316,8 @@ static void ShowExampleAppConstrainedResize(bool* p_open) else { ImGui::Text("(Hold SHIFT to display a dummy viewport)"); + if (ImGui::IsWindowDocked()) + ImGui::Text("Warning: Sizing Constraints won't work if the window is docked!"); if (ImGui::Button("Set 200x200")) { ImGui::SetWindowSize(ImVec2(200, 200)); } ImGui::SameLine(); if (ImGui::Button("Set 500x500")) { ImGui::SetWindowSize(ImVec2(500, 500)); } ImGui::SameLine(); if (ImGui::Button("Set 800x200")) { ImGui::SetWindowSize(ImVec2(800, 200)); } @@ -7998,7 +9344,7 @@ static void ShowExampleAppSimpleOverlay(bool* p_open) { static int location = 0; ImGuiIO& io = ImGui::GetIO(); - ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; if (location >= 0) { const float PAD = 10.0f; @@ -8011,6 +9357,7 @@ static void ShowExampleAppSimpleOverlay(bool* p_open) window_pos_pivot.x = (location & 1) ? 1.0f : 0.0f; window_pos_pivot.y = (location & 2) ? 1.0f : 0.0f; ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); + ImGui::SetNextWindowViewport(viewport->ID); window_flags |= ImGuiWindowFlags_NoMove; } else if (location == -2) @@ -8289,7 +9636,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) // To use a child window instead we could use, e.g: // ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); // Disable padding // ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(50, 50, 50, 255)); // Set a background color - // ImGui::BeginChild("canvas", ImVec2(0.0f, 0.0f), ImGuiChildFlags_Border, ImGuiWindowFlags_NoMove); + // ImGui::BeginChild("canvas", ImVec2(0.0f, 0.0f), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove); // ImGui::PopStyleColor(); // ImGui::PopStyleVar(); // [...] @@ -8430,6 +9777,134 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::End(); } +//----------------------------------------------------------------------------- +// [SECTION] Example App: Docking, DockSpace / ShowExampleAppDockSpace() +//----------------------------------------------------------------------------- + +// Demonstrate using DockSpace() to create an explicit docking node within an existing window. +// Note: You can use most Docking facilities without calling any API. You DO NOT need to call DockSpace() to use Docking! +// - Drag from window title bar or their tab to dock/undock. Hold SHIFT to disable docking. +// - Drag from window menu button (upper-left button) to undock an entire node (all windows). +// - When io.ConfigDockingWithShift == true, you instead need to hold SHIFT to enable docking. +// About dockspaces: +// - Use DockSpace() to create an explicit dock node _within_ an existing window. +// - Use DockSpaceOverViewport() to create an explicit dock node covering the screen or a specific viewport. +// This is often used with ImGuiDockNodeFlags_PassthruCentralNode. +// - Important: Dockspaces need to be submitted _before_ any window they can host. Submit it early in your frame! (*) +// - Important: Dockspaces need to be kept alive if hidden, otherwise windows docked into it will be undocked. +// e.g. if you have multiple tabs with a dockspace inside each tab: submit the non-visible dockspaces with ImGuiDockNodeFlags_KeepAliveOnly. +// (*) because of this constraint, the implicit \"Debug\" window can not be docked into an explicit DockSpace() node, +// because that window is submitted as part of the part of the NewFrame() call. An easy workaround is that you can create +// your own implicit "Debug##2" window after calling DockSpace() and leave it in the window stack for anyone to use. +void ShowExampleAppDockSpace(bool* p_open) +{ + // READ THIS !!! + // TL;DR; this demo is more complicated than what most users you would normally use. + // If we remove all options we are showcasing, this demo would become: + // void ShowExampleAppDockSpace() + // { + // ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport()); + // } + // In most cases you should be able to just call DockSpaceOverViewport() and ignore all the code below! + // In this specific demo, we are not using DockSpaceOverViewport() because: + // - (1) we allow the host window to be floating/moveable instead of filling the viewport (when opt_fullscreen == false) + // - (2) we allow the host window to have padding (when opt_padding == true) + // - (3) we expose many flags and need a way to have them visible. + // - (4) we have a local menu bar in the host window (vs. you could use BeginMainMenuBar() + DockSpaceOverViewport() + // in your code, but we don't here because we allow the window to be floating) + + static bool opt_fullscreen = true; + static bool opt_padding = false; + static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None; + + // We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into, + // because it would be confusing to have two docking targets within each others. + ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; + if (opt_fullscreen) + { + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->WorkPos); + ImGui::SetNextWindowSize(viewport->WorkSize); + ImGui::SetNextWindowViewport(viewport->ID); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; + window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + } + else + { + dockspace_flags &= ~ImGuiDockNodeFlags_PassthruCentralNode; + } + + // When using ImGuiDockNodeFlags_PassthruCentralNode, DockSpace() will render our background + // and handle the pass-thru hole, so we ask Begin() to not render a background. + if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) + window_flags |= ImGuiWindowFlags_NoBackground; + + // Important: note that we proceed even if Begin() returns false (aka window is collapsed). + // This is because we want to keep our DockSpace() active. If a DockSpace() is inactive, + // all active windows docked into it will lose their parent and become undocked. + // We cannot preserve the docking relationship between an active window and an inactive docking, otherwise + // any change of dockspace/settings would lead to windows being stuck in limbo and never being visible. + if (!opt_padding) + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::Begin("DockSpace Demo", p_open, window_flags); + if (!opt_padding) + ImGui::PopStyleVar(); + + if (opt_fullscreen) + ImGui::PopStyleVar(2); + + // Submit the DockSpace + ImGuiIO& io = ImGui::GetIO(); + if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) + { + ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); + ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); + } + else + { + ShowDockingDisabledMessage(); + } + + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Options")) + { + // Disabling fullscreen would allow the window to be moved to the front of other windows, + // which we can't undo at the moment without finer window depth/z control. + ImGui::MenuItem("Fullscreen", NULL, &opt_fullscreen); + ImGui::MenuItem("Padding", NULL, &opt_padding); + ImGui::Separator(); + + if (ImGui::MenuItem("Flag: NoDockingOverCentralNode", "", (dockspace_flags & ImGuiDockNodeFlags_NoDockingOverCentralNode) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoDockingOverCentralNode; } + if (ImGui::MenuItem("Flag: NoDockingSplit", "", (dockspace_flags & ImGuiDockNodeFlags_NoDockingSplit) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoDockingSplit; } + if (ImGui::MenuItem("Flag: NoUndocking", "", (dockspace_flags & ImGuiDockNodeFlags_NoUndocking) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoUndocking; } + if (ImGui::MenuItem("Flag: NoResize", "", (dockspace_flags & ImGuiDockNodeFlags_NoResize) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoResize; } + if (ImGui::MenuItem("Flag: AutoHideTabBar", "", (dockspace_flags & ImGuiDockNodeFlags_AutoHideTabBar) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_AutoHideTabBar; } + if (ImGui::MenuItem("Flag: PassthruCentralNode", "", (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0, opt_fullscreen)) { dockspace_flags ^= ImGuiDockNodeFlags_PassthruCentralNode; } + ImGui::Separator(); + + if (ImGui::MenuItem("Close", NULL, false, p_open != NULL)) + *p_open = false; + ImGui::EndMenu(); + } + HelpMarker( + "When docking is enabled, you can ALWAYS dock MOST window into another! Try it now!" "\n" + "- Drag from window title bar or their tab to dock/undock." "\n" + "- Drag from window menu button (upper-left button) to undock an entire node (all windows)." "\n" + "- Hold SHIFT to disable docking (if io.ConfigDockingWithShift == false, default)" "\n" + "- Hold SHIFT to enable docking (if io.ConfigDockingWithShift == true)" "\n" + "This demo app has nothing to do with enabling docking!" "\n\n" + "This demo app only demonstrate the use of ImGui::DockSpace() which allows you to manually create a docking node _within_ another window." "\n\n" + "Read comments in ShowExampleAppDockSpace() for more details."); + + ImGui::EndMenuBar(); + } + + ImGui::End(); +} + //----------------------------------------------------------------------------- // [SECTION] Example App: Documents Handling / ShowExampleAppDocuments() //----------------------------------------------------------------------------- @@ -8555,11 +10030,25 @@ void ShowExampleAppDocuments(bool* p_open) static ExampleAppDocuments app; // Options + enum Target + { + Target_None, + Target_Tab, // Create documents as local tab into a local tab bar + Target_DockSpaceAndWindow // Create documents as regular windows, and create an embedded dockspace + }; + static Target opt_target = Target_Tab; static bool opt_reorderable = true; static ImGuiTabBarFlags opt_fitting_flags = ImGuiTabBarFlags_FittingPolicyDefault_; + // When (opt_target == Target_DockSpaceAndWindow) there is the possibily that one of our child Document window (e.g. "Eggplant") + // that we emit gets docked into the same spot as the parent window ("Example: Documents"). + // This would create a problematic feedback loop because selecting the "Eggplant" tab would make the "Example: Documents" tab + // not visible, which in turn would stop submitting the "Eggplant" window. + // We avoid this problem by submitting our documents window even if our parent window is not currently visible. + // Another solution may be to make the "Example: Documents" window use the ImGuiWindowFlags_NoDocking. + bool window_contents_visible = ImGui::Begin("Example: Documents", p_open, ImGuiWindowFlags_MenuBar); - if (!window_contents_visible) + if (!window_contents_visible && opt_target != Target_DockSpaceAndWindow) { ImGui::End(); return; @@ -8603,6 +10092,12 @@ void ShowExampleAppDocuments(bool* p_open) doc.DoForceClose(); ImGui::PopID(); } + ImGui::PushItemWidth(ImGui::GetFontSize() * 12); + ImGui::Combo("Output", (int*)&opt_target, "None\0TabBar+Tabs\0DockSpace+Window\0"); + ImGui::PopItemWidth(); + bool redock_all = false; + if (opt_target == Target_Tab) { ImGui::SameLine(); ImGui::Checkbox("Reorderable Tabs", &opt_reorderable); } + if (opt_target == Target_DockSpaceAndWindow) { ImGui::SameLine(); redock_all = ImGui::Button("Redock all"); } ImGui::Separator(); @@ -8616,9 +10111,11 @@ void ShowExampleAppDocuments(bool* p_open) // hole for one-frame, both in the tab-bar and in tab-contents when closing a tab/window. // The rarely used SetTabItemClosed() function is a way to notify of programmatic closure to avoid the one-frame hole. - // Submit Tab Bar and Tabs + // Tabs + if (opt_target == Target_Tab) { ImGuiTabBarFlags tab_bar_flags = (opt_fitting_flags) | (opt_reorderable ? ImGuiTabBarFlags_Reorderable : 0); + tab_bar_flags |= ImGuiTabBarFlags_DrawSelectedOverline; if (ImGui::BeginTabBar("##tabs", tab_bar_flags)) { if (opt_reorderable) @@ -8658,6 +10155,53 @@ void ShowExampleAppDocuments(bool* p_open) ImGui::EndTabBar(); } } + else if (opt_target == Target_DockSpaceAndWindow) + { + if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_DockingEnable) + { + app.NotifyOfDocumentsClosedElsewhere(); + + // Create a DockSpace node where any window can be docked + ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); + ImGui::DockSpace(dockspace_id); + + // Create Windows + for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) + { + MyDocument* doc = &app.Documents[doc_n]; + if (!doc->Open) + continue; + + ImGui::SetNextWindowDockID(dockspace_id, redock_all ? ImGuiCond_Always : ImGuiCond_FirstUseEver); + ImGuiWindowFlags window_flags = (doc->Dirty ? ImGuiWindowFlags_UnsavedDocument : 0); + bool visible = ImGui::Begin(doc->Name, &doc->Open, window_flags); + + // Cancel attempt to close when unsaved add to save queue so we can display a popup. + if (!doc->Open && doc->Dirty) + { + doc->Open = true; + app.CloseQueue.push_back(doc); + } + + app.DisplayDocContextMenu(doc); + if (visible) + app.DisplayDocContents(doc); + + ImGui::End(); + } + } + else + { + ShowDockingDisabledMessage(); + } + } + + // Early out other contents + if (!window_contents_visible) + { + ImGui::End(); + return; + } // Display renaming UI if (app.RenamingDoc != NULL) @@ -8746,6 +10290,404 @@ void ShowExampleAppDocuments(bool* p_open) ImGui::End(); } +//----------------------------------------------------------------------------- +// [SECTION] Example App: Assets Browser / ShowExampleAppAssetsBrowser() +//----------------------------------------------------------------------------- + +//#include "imgui_internal.h" // NavMoveRequestTryWrapping() + +struct ExampleAsset +{ + ImGuiID ID; + int Type; + + ExampleAsset(ImGuiID id, int type) { ID = id; Type = type; } + + static const ImGuiTableSortSpecs* s_current_sort_specs; + + static void SortWithSortSpecs(ImGuiTableSortSpecs* sort_specs, ExampleAsset* items, int items_count) + { + s_current_sort_specs = sort_specs; // Store in variable accessible by the sort function. + if (items_count > 1) + qsort(items, (size_t)items_count, sizeof(items[0]), ExampleAsset::CompareWithSortSpecs); + s_current_sort_specs = NULL; + } + + // Compare function to be used by qsort() + static int IMGUI_CDECL CompareWithSortSpecs(const void* lhs, const void* rhs) + { + const ExampleAsset* a = (const ExampleAsset*)lhs; + const ExampleAsset* b = (const ExampleAsset*)rhs; + for (int n = 0; n < s_current_sort_specs->SpecsCount; n++) + { + const ImGuiTableColumnSortSpecs* sort_spec = &s_current_sort_specs->Specs[n]; + int delta = 0; + if (sort_spec->ColumnIndex == 0) + delta = ((int)a->ID - (int)b->ID); + else if (sort_spec->ColumnIndex == 1) + delta = (a->Type - b->Type); + if (delta > 0) + return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? +1 : -1; + if (delta < 0) + return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? -1 : +1; + } + return ((int)a->ID - (int)b->ID); + } +}; +const ImGuiTableSortSpecs* ExampleAsset::s_current_sort_specs = NULL; + +struct ExampleAssetsBrowser +{ + // Options + bool ShowTypeOverlay = true; + bool AllowSorting = true; + bool AllowDragUnselected = false; + bool AllowBoxSelect = true; + float IconSize = 32.0f; + int IconSpacing = 10; + int IconHitSpacing = 4; // Increase hit-spacing if you want to make it possible to clear or box-select from gaps. Some spacing is required to able to amend with Shift+box-select. Value is small in Explorer. + bool StretchSpacing = true; + + // State + ImVector Items; // Our items + ExampleSelectionWithDeletion Selection; // Our selection (ImGuiSelectionBasicStorage + helper funcs to handle deletion) + ImGuiID NextItemId = 0; // Unique identifier when creating new items + bool RequestDelete = false; // Deferred deletion request + bool RequestSort = false; // Deferred sort request + float ZoomWheelAccum = 0.0f; // Mouse wheel accumulator to handle smooth wheels better + + // Calculated sizes for layout, output of UpdateLayoutSizes(). Could be locals but our code is simpler this way. + ImVec2 LayoutItemSize; + ImVec2 LayoutItemStep; // == LayoutItemSize + LayoutItemSpacing + float LayoutItemSpacing = 0.0f; + float LayoutSelectableSpacing = 0.0f; + float LayoutOuterPadding = 0.0f; + int LayoutColumnCount = 0; + int LayoutLineCount = 0; + + // Functions + ExampleAssetsBrowser() + { + AddItems(10000); + } + void AddItems(int count) + { + if (Items.Size == 0) + NextItemId = 0; + Items.reserve(Items.Size + count); + for (int n = 0; n < count; n++, NextItemId++) + Items.push_back(ExampleAsset(NextItemId, (NextItemId % 20) < 15 ? 0 : (NextItemId % 20) < 18 ? 1 : 2)); + RequestSort = true; + } + void ClearItems() + { + Items.clear(); + Selection.Clear(); + } + + // Logic would be written in the main code BeginChild() and outputing to local variables. + // We extracted it into a function so we can call it easily from multiple places. + void UpdateLayoutSizes(float avail_width) + { + // Layout: when not stretching: allow extending into right-most spacing. + LayoutItemSpacing = (float)IconSpacing; + if (StretchSpacing == false) + avail_width += floorf(LayoutItemSpacing * 0.5f); + + // Layout: calculate number of icon per line and number of lines + LayoutItemSize = ImVec2(floorf(IconSize), floorf(IconSize)); + LayoutColumnCount = IM_MAX((int)(avail_width / (LayoutItemSize.x + LayoutItemSpacing)), 1); + LayoutLineCount = (Items.Size + LayoutColumnCount - 1) / LayoutColumnCount; + + // Layout: when stretching: allocate remaining space to more spacing. Round before division, so item_spacing may be non-integer. + if (StretchSpacing && LayoutColumnCount > 1) + LayoutItemSpacing = floorf(avail_width - LayoutItemSize.x * LayoutColumnCount) / LayoutColumnCount; + + LayoutItemStep = ImVec2(LayoutItemSize.x + LayoutItemSpacing, LayoutItemSize.y + LayoutItemSpacing); + LayoutSelectableSpacing = IM_MAX(floorf(LayoutItemSpacing) - IconHitSpacing, 0.0f); + LayoutOuterPadding = floorf(LayoutItemSpacing * 0.5f); + } + + void Draw(const char* title, bool* p_open) + { + ImGui::SetNextWindowSize(ImVec2(IconSize * 25, IconSize * 15), ImGuiCond_FirstUseEver); + if (!ImGui::Begin(title, p_open, ImGuiWindowFlags_MenuBar)) + { + ImGui::End(); + return; + } + + // Menu bar + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem("Add 10000 items")) + AddItems(10000); + if (ImGui::MenuItem("Clear items")) + ClearItems(); + ImGui::Separator(); + if (ImGui::MenuItem("Close", NULL, false, p_open != NULL)) + *p_open = false; + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Edit")) + { + if (ImGui::MenuItem("Delete", "Del", false, Selection.Size > 0)) + RequestDelete = true; + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Options")) + { + ImGui::PushItemWidth(ImGui::GetFontSize() * 10); + + ImGui::SeparatorText("Contents"); + ImGui::Checkbox("Show Type Overlay", &ShowTypeOverlay); + ImGui::Checkbox("Allow Sorting", &AllowSorting); + + ImGui::SeparatorText("Selection Behavior"); + ImGui::Checkbox("Allow dragging unselected item", &AllowDragUnselected); + ImGui::Checkbox("Allow box-selection", &AllowBoxSelect); + + ImGui::SeparatorText("Layout"); + ImGui::SliderFloat("Icon Size", &IconSize, 16.0f, 128.0f, "%.0f"); + ImGui::SameLine(); HelpMarker("Use CTRL+Wheel to zoom"); + ImGui::SliderInt("Icon Spacing", &IconSpacing, 0, 32); + ImGui::SliderInt("Icon Hit Spacing", &IconHitSpacing, 0, 32); + ImGui::Checkbox("Stretch Spacing", &StretchSpacing); + ImGui::PopItemWidth(); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + // Show a table with ONLY one header row to showcase the idea/possibility of using this to provide a sorting UI + if (AllowSorting) + { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGuiTableFlags table_flags_for_sort_specs = ImGuiTableFlags_Sortable | ImGuiTableFlags_SortMulti | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Borders; + if (ImGui::BeginTable("for_sort_specs_only", 2, table_flags_for_sort_specs, ImVec2(0.0f, ImGui::GetFrameHeight()))) + { + ImGui::TableSetupColumn("Index"); + ImGui::TableSetupColumn("Type"); + ImGui::TableHeadersRow(); + if (ImGuiTableSortSpecs* sort_specs = ImGui::TableGetSortSpecs()) + if (sort_specs->SpecsDirty || RequestSort) + { + ExampleAsset::SortWithSortSpecs(sort_specs, Items.Data, Items.Size); + sort_specs->SpecsDirty = RequestSort = false; + } + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + } + + ImGuiIO& io = ImGui::GetIO(); + ImGui::SetNextWindowContentSize(ImVec2(0.0f, LayoutOuterPadding + LayoutLineCount * (LayoutItemSize.y + LayoutItemSpacing))); + if (ImGui::BeginChild("Assets", ImVec2(0.0f, -ImGui::GetTextLineHeightWithSpacing()), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove)) + { + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + const float avail_width = ImGui::GetContentRegionAvail().x; + UpdateLayoutSizes(avail_width); + + // Calculate and store start position. + ImVec2 start_pos = ImGui::GetCursorScreenPos(); + start_pos = ImVec2(start_pos.x + LayoutOuterPadding, start_pos.y + LayoutOuterPadding); + ImGui::SetCursorScreenPos(start_pos); + + // Multi-select + ImGuiMultiSelectFlags ms_flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_ClearOnClickVoid; + + // - Enable box-select (in 2D mode, so that changing box-select rectangle X1/X2 boundaries will affect clipped items) + if (AllowBoxSelect) + ms_flags |= ImGuiMultiSelectFlags_BoxSelect2d; + + // - This feature allows dragging an unselected item without selecting it (rarely used) + if (AllowDragUnselected) + ms_flags |= ImGuiMultiSelectFlags_SelectOnClickRelease; + + // - Enable keyboard wrapping on X axis + // (FIXME-MULTISELECT: We haven't designed/exposed a general nav wrapping api yet, so this flag is provided as a courtesy to avoid doing: + // ImGui::NavMoveRequestTryWrapping(ImGui::GetCurrentWindow(), ImGuiNavMoveFlags_WrapX); + // When we finish implementing a more general API for this, we will obsolete this flag in favor of the new system) + ms_flags |= ImGuiMultiSelectFlags_NavWrapX; + + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(ms_flags, Selection.Size, Items.Size); + + // Use custom selection adapter: store ID in selection (recommended) + Selection.UserData = this; + Selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self_, int idx) { ExampleAssetsBrowser* self = (ExampleAssetsBrowser*)self_->UserData; return self->Items[idx].ID; }; + Selection.ApplyRequests(ms_io); + + const bool want_delete = (ImGui::Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && (Selection.Size > 0)) || RequestDelete; + const int item_curr_idx_to_focus = want_delete ? Selection.ApplyDeletionPreLoop(ms_io, Items.Size) : -1; + RequestDelete = false; + + // Push LayoutSelectableSpacing (which is LayoutItemSpacing minus hit-spacing, if we decide to have hit gaps between items) + // Altering style ItemSpacing may seem unnecessary as we position every items using SetCursorScreenPos()... + // But it is necessary for two reasons: + // - Selectables uses it by default to visually fill the space between two items. + // - The vertical spacing would be measured by Clipper to calculate line height if we didn't provide it explicitly (here we do). + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(LayoutSelectableSpacing, LayoutSelectableSpacing)); + + // Rendering parameters + const ImU32 icon_type_overlay_colors[3] = { 0, IM_COL32(200, 70, 70, 255), IM_COL32(70, 170, 70, 255) }; + const ImU32 icon_bg_color = ImGui::GetColorU32(IM_COL32(35, 35, 35, 220)); + const ImVec2 icon_type_overlay_size = ImVec2(4.0f, 4.0f); + const bool display_label = (LayoutItemSize.x >= ImGui::CalcTextSize("999").x); + + const int column_count = LayoutColumnCount; + ImGuiListClipper clipper; + clipper.Begin(LayoutLineCount, LayoutItemStep.y); + if (item_curr_idx_to_focus != -1) + clipper.IncludeItemByIndex(item_curr_idx_to_focus / column_count); // Ensure focused item line is not clipped. + if (ms_io->RangeSrcItem != -1) + clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem / column_count); // Ensure RangeSrc item line is not clipped. + while (clipper.Step()) + { + for (int line_idx = clipper.DisplayStart; line_idx < clipper.DisplayEnd; line_idx++) + { + const int item_min_idx_for_current_line = line_idx * column_count; + const int item_max_idx_for_current_line = IM_MIN((line_idx + 1) * column_count, Items.Size); + for (int item_idx = item_min_idx_for_current_line; item_idx < item_max_idx_for_current_line; ++item_idx) + { + ExampleAsset* item_data = &Items[item_idx]; + ImGui::PushID((int)item_data->ID); + + // Position item + ImVec2 pos = ImVec2(start_pos.x + (item_idx % column_count) * LayoutItemStep.x, start_pos.y + line_idx * LayoutItemStep.y); + ImGui::SetCursorScreenPos(pos); + + ImGui::SetNextItemSelectionUserData(item_idx); + bool item_is_selected = Selection.Contains((ImGuiID)item_data->ID); + bool item_is_visible = ImGui::IsRectVisible(LayoutItemSize); + ImGui::Selectable("", item_is_selected, ImGuiSelectableFlags_None, LayoutItemSize); + + // Update our selection state immediately (without waiting for EndMultiSelect() requests) + // because we use this to alter the color of our text/icon. + if (ImGui::IsItemToggledSelection()) + item_is_selected = !item_is_selected; + + // Focus (for after deletion) + if (item_curr_idx_to_focus == item_idx) + ImGui::SetKeyboardFocusHere(-1); + + // Drag and drop + if (ImGui::BeginDragDropSource()) + { + // Create payload with full selection OR single unselected item. + // (the later is only possible when using ImGuiMultiSelectFlags_SelectOnClickRelease) + if (ImGui::GetDragDropPayload() == NULL) + { + ImVector payload_items; + void* it = NULL; + ImGuiID id = 0; + if (!item_is_selected) + payload_items.push_back(item_data->ID); + else + while (Selection.GetNextSelectedItem(&it, &id)) + payload_items.push_back(id); + ImGui::SetDragDropPayload("ASSETS_BROWSER_ITEMS", payload_items.Data, (size_t)payload_items.size_in_bytes()); + } + + // Display payload content in tooltip, by extracting it from the payload data + // (we could read from selection, but it is more correct and reusable to read from payload) + const ImGuiPayload* payload = ImGui::GetDragDropPayload(); + const int payload_count = (int)payload->DataSize / (int)sizeof(ImGuiID); + ImGui::Text("%d assets", payload_count); + + ImGui::EndDragDropSource(); + } + + // Render icon (a real app would likely display an image/thumbnail here) + // Because we use ImGuiMultiSelectFlags_BoxSelect2d, clipping vertical may occasionally be larger, so we coarse-clip our rendering as well. + if (item_is_visible) + { + ImVec2 box_min(pos.x - 1, pos.y - 1); + ImVec2 box_max(box_min.x + LayoutItemSize.x + 2, box_min.y + LayoutItemSize.y + 2); // Dubious + draw_list->AddRectFilled(box_min, box_max, icon_bg_color); // Background color + if (ShowTypeOverlay && item_data->Type != 0) + { + ImU32 type_col = icon_type_overlay_colors[item_data->Type % IM_ARRAYSIZE(icon_type_overlay_colors)]; + draw_list->AddRectFilled(ImVec2(box_max.x - 2 - icon_type_overlay_size.x, box_min.y + 2), ImVec2(box_max.x - 2, box_min.y + 2 + icon_type_overlay_size.y), type_col); + } + if (display_label) + { + ImU32 label_col = ImGui::GetColorU32(item_is_selected ? ImGuiCol_Text : ImGuiCol_TextDisabled); + char label[32]; + sprintf(label, "%d", item_data->ID); + draw_list->AddText(ImVec2(box_min.x, box_max.y - ImGui::GetFontSize()), label_col, label); + } + } + + ImGui::PopID(); + } + } + } + clipper.End(); + ImGui::PopStyleVar(); // ImGuiStyleVar_ItemSpacing + + // Context menu + if (ImGui::BeginPopupContextWindow()) + { + ImGui::Text("Selection: %d items", Selection.Size); + ImGui::Separator(); + if (ImGui::MenuItem("Delete", "Del", false, Selection.Size > 0)) + RequestDelete = true; + ImGui::EndPopup(); + } + + ms_io = ImGui::EndMultiSelect(); + Selection.ApplyRequests(ms_io); + if (want_delete) + Selection.ApplyDeletionPostLoop(ms_io, Items, item_curr_idx_to_focus); + + // Zooming with CTRL+Wheel + if (ImGui::IsWindowAppearing()) + ZoomWheelAccum = 0.0f; + if (ImGui::IsWindowHovered() && io.MouseWheel != 0.0f && ImGui::IsKeyDown(ImGuiMod_Ctrl) && ImGui::IsAnyItemActive() == false) + { + ZoomWheelAccum += io.MouseWheel; + if (fabsf(ZoomWheelAccum) >= 1.0f) + { + // Calculate hovered item index from mouse location + // FIXME: Locking aiming on 'hovered_item_idx' (with a cool-down timer) would ensure zoom keeps on it. + const float hovered_item_nx = (io.MousePos.x - start_pos.x + LayoutItemSpacing * 0.5f) / LayoutItemStep.x; + const float hovered_item_ny = (io.MousePos.y - start_pos.y + LayoutItemSpacing * 0.5f) / LayoutItemStep.y; + const int hovered_item_idx = ((int)hovered_item_ny * LayoutColumnCount) + (int)hovered_item_nx; + //ImGui::SetTooltip("%f,%f -> item %d", hovered_item_nx, hovered_item_ny, hovered_item_idx); // Move those 4 lines in block above for easy debugging + + // Zoom + IconSize *= powf(1.1f, (float)(int)ZoomWheelAccum); + IconSize = IM_CLAMP(IconSize, 16.0f, 128.0f); + ZoomWheelAccum -= (int)ZoomWheelAccum; + UpdateLayoutSizes(avail_width); + + // Manipulate scroll to that we will land at the same Y location of currently hovered item. + // - Calculate next frame position of item under mouse + // - Set new scroll position to be used in next ImGui::BeginChild() call. + float hovered_item_rel_pos_y = ((float)(hovered_item_idx / LayoutColumnCount) + fmodf(hovered_item_ny, 1.0f)) * LayoutItemStep.y; + hovered_item_rel_pos_y += ImGui::GetStyle().WindowPadding.y; + float mouse_local_y = io.MousePos.y - ImGui::GetWindowPos().y; + ImGui::SetScrollY(hovered_item_rel_pos_y - mouse_local_y); + } + } + } + ImGui::EndChild(); + + ImGui::Text("Selected: %d/%d items", Selection.Size, Items.Size); + ImGui::End(); + } +}; + +void ShowExampleAppAssetsBrowser(bool* p_open) +{ + IMGUI_DEMO_MARKER("Examples/Assets Browser"); + static ExampleAssetsBrowser assets_browser; + assets_browser.Draw("Example: Assets Browser", p_open); +} + // End of Demo code #else @@ -8753,6 +10695,8 @@ void ImGui::ShowAboutWindow(bool*) {} void ImGui::ShowDemoWindow(bool*) {} void ImGui::ShowUserGuide() {} void ImGui::ShowStyleEditor(ImGuiStyle*) {} +bool ImGui::ShowStyleSelector(const char* label) { return false; } +void ImGui::ShowFontSelector(const char* label) {} #endif diff --git a/ext/imgui/imgui_draw.cpp b/ext/imgui/imgui_draw.cpp index 93828369f973..c86237298d37 100644 --- a/ext/imgui/imgui_draw.cpp +++ b/ext/imgui/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.9 WIP +// dear imgui, v1.91.6 // (drawing and font code) /* @@ -14,7 +14,7 @@ Index of this file: // [SECTION] Helpers ShadeVertsXXX functions // [SECTION] ImFontConfig // [SECTION] ImFontAtlas -// [SECTION] ImFontAtlas glyph ranges helpers +// [SECTION] ImFontAtlas: glyph ranges helpers // [SECTION] ImFontGlyphRangesBuilder // [SECTION] ImFont // [SECTION] ImGui Internal Render Helpers @@ -23,8 +23,6 @@ Index of this file: */ -#undef new - #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif @@ -68,13 +66,14 @@ Index of this file: #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wreserved-identifier" // warning: identifier '_Xxx' is reserved because it starts with '_' followed by a capital letter #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type #elif defined(__GNUC__) -#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind -#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used -#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function -#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value -#pragma GCC diagnostic ignored "-Wstack-protector" // warning: stack protector not protecting local variables: variable length buffer -#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wstack-protector" // warning: stack protector not protecting local variables: variable length buffer +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #endif //------------------------------------------------------------------------- @@ -103,7 +102,7 @@ namespace IMGUI_STB_NAMESPACE #if defined(__clang__) #pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-function" +#pragma clang diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used #pragma clang diagnostic ignored "-Wmissing-prototypes" #pragma clang diagnostic ignored "-Wimplicit-fallthrough" #pragma clang diagnostic ignored "-Wcast-qual" // warning: cast from 'const xxxx *' to 'xxx *' drops const qualifier @@ -213,11 +212,15 @@ void ImGui::StyleColorsDark(ImGuiStyle* dst) colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.20f); colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); - colors[ImGuiCol_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.80f); colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered]; - colors[ImGuiCol_TabActive] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); - colors[ImGuiCol_TabUnfocused] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); - colors[ImGuiCol_TabUnfocusedActive] = ImLerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.80f); + colors[ImGuiCol_TabSelected] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); + colors[ImGuiCol_TabSelectedOverline] = colors[ImGuiCol_HeaderActive]; + colors[ImGuiCol_TabDimmed] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); + colors[ImGuiCol_TabDimmedSelected] = ImLerp(colors[ImGuiCol_TabSelected], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.50f, 0.50f, 0.50f, 0.00f); + colors[ImGuiCol_DockingPreview] = colors[ImGuiCol_HeaderActive] * ImVec4(1.0f, 1.0f, 1.0f, 0.7f); + colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); @@ -227,9 +230,10 @@ void ImGui::StyleColorsDark(ImGuiStyle* dst) colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.25f, 1.00f); // Prefer using Alpha=1.0 here colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f); + colors[ImGuiCol_TextLink] = colors[ImGuiCol_HeaderActive]; colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); - colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_NavCursor] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); @@ -273,11 +277,15 @@ void ImGui::StyleColorsClassic(ImGuiStyle* dst) colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.10f); colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.78f, 0.82f, 1.00f, 0.60f); colors[ImGuiCol_ResizeGripActive] = ImVec4(0.78f, 0.82f, 1.00f, 0.90f); - colors[ImGuiCol_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.80f); colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered]; - colors[ImGuiCol_TabActive] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); - colors[ImGuiCol_TabUnfocused] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); - colors[ImGuiCol_TabUnfocusedActive] = ImLerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.80f); + colors[ImGuiCol_TabSelected] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); + colors[ImGuiCol_TabSelectedOverline] = colors[ImGuiCol_HeaderActive]; + colors[ImGuiCol_TabDimmed] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); + colors[ImGuiCol_TabDimmedSelected] = ImLerp(colors[ImGuiCol_TabSelected], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.53f, 0.53f, 0.87f, 0.00f); + colors[ImGuiCol_DockingPreview] = colors[ImGuiCol_Header] * ImVec4(1.0f, 1.0f, 1.0f, 0.7f); + colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); @@ -287,9 +295,10 @@ void ImGui::StyleColorsClassic(ImGuiStyle* dst) colors[ImGuiCol_TableBorderLight] = ImVec4(0.26f, 0.26f, 0.28f, 1.00f); // Prefer using Alpha=1.0 here colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f); + colors[ImGuiCol_TextLink] = colors[ImGuiCol_HeaderActive]; colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f); colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); - colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_NavCursor] = colors[ImGuiCol_HeaderHovered]; colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); @@ -334,11 +343,15 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) colors[ImGuiCol_ResizeGrip] = ImVec4(0.35f, 0.35f, 0.35f, 0.17f); colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); - colors[ImGuiCol_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.90f); colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered]; - colors[ImGuiCol_TabActive] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); - colors[ImGuiCol_TabUnfocused] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); - colors[ImGuiCol_TabUnfocusedActive] = ImLerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.90f); + colors[ImGuiCol_TabSelected] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); + colors[ImGuiCol_TabSelectedOverline] = colors[ImGuiCol_HeaderActive]; + colors[ImGuiCol_TabDimmed] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); + colors[ImGuiCol_TabDimmedSelected] = ImLerp(colors[ImGuiCol_TabSelected], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.26f, 0.59f, 1.00f, 0.00f); + colors[ImGuiCol_DockingPreview] = colors[ImGuiCol_Header] * ImVec4(1.0f, 1.0f, 1.0f, 0.7f); + colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); colors[ImGuiCol_PlotLines] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); @@ -348,9 +361,10 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) colors[ImGuiCol_TableBorderLight] = ImVec4(0.68f, 0.68f, 0.74f, 1.00f); // Prefer using Alpha=1.0 here colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); colors[ImGuiCol_TableRowBgAlt] = ImVec4(0.30f, 0.30f, 0.30f, 0.09f); + colors[ImGuiCol_TextLink] = colors[ImGuiCol_HeaderActive]; colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); colors[ImGuiCol_DragDropTarget] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); - colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_NavCursor] = colors[ImGuiCol_HeaderHovered]; colors[ImGuiCol_NavWindowingHighlight] = ImVec4(0.70f, 0.70f, 0.70f, 0.70f); colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.20f); colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); @@ -386,6 +400,17 @@ void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); } +ImDrawList::ImDrawList(ImDrawListSharedData* shared_data) +{ + memset(this, 0, sizeof(*this)); + _Data = shared_data; +} + +ImDrawList::~ImDrawList() +{ + _ClearFreeMemory(); +} + // Initialize before use in a new frame. We always have a command ready in the buffer. // In the majority of cases, you would want to call PushClipRect() and PushTextureID() after this. void ImDrawList::_ResetForNewFrame() @@ -407,6 +432,7 @@ void ImDrawList::_ResetForNewFrame() _IdxWritePtr = NULL; _ClipRectStack.resize(0); _TextureIdStack.resize(0); + _CallbacksDataBuf.resize(0); _Path.resize(0); _Splitter.Clear(); CmdBuffer.push_back(ImDrawCmd()); @@ -424,6 +450,7 @@ void ImDrawList::_ClearFreeMemory() _IdxWritePtr = NULL; _ClipRectStack.clear(); _TextureIdStack.clear(); + _CallbacksDataBuf.clear(); _Path.clear(); _Splitter.ClearFreeMemory(); } @@ -463,7 +490,7 @@ void ImDrawList::_PopUnusedDrawCmd() } } -void ImDrawList::AddCallback(ImDrawCallback callback, void* callback_data) +void ImDrawList::AddCallback(ImDrawCallback callback, void* userdata, size_t userdata_size) { IM_ASSERT_PARANOID(CmdBuffer.Size > 0); ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; @@ -473,8 +500,26 @@ void ImDrawList::AddCallback(ImDrawCallback callback, void* callback_data) AddDrawCmd(); curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; } + curr_cmd->UserCallback = callback; - curr_cmd->UserCallbackData = callback_data; + if (userdata_size == 0) + { + // Store user data directly in command (no indirection) + curr_cmd->UserCallbackData = userdata; + curr_cmd->UserCallbackDataSize = 0; + curr_cmd->UserCallbackDataOffset = -1; + } + else + { + // Copy and store user data in a buffer + IM_ASSERT(userdata != NULL); + IM_ASSERT(userdata_size < (1u << 31)); + curr_cmd->UserCallbackData = NULL; // Will be resolved during Render() + curr_cmd->UserCallbackDataSize = (int)userdata_size; + curr_cmd->UserCallbackDataOffset = _CallbacksDataBuf.Size; + _CallbacksDataBuf.resize(_CallbacksDataBuf.Size + (int)userdata_size); + memcpy(_CallbacksDataBuf.Data + (size_t)curr_cmd->UserCallbackDataOffset, userdata, userdata_size); + } AddDrawCmd(); // Force a new command after us (see comment below) } @@ -519,7 +564,6 @@ void ImDrawList::_OnChangedClipRect() CmdBuffer.pop_back(); return; } - curr_cmd->ClipRect = _CmdHeader.ClipRect; } @@ -542,7 +586,6 @@ void ImDrawList::_OnChangedTextureID() CmdBuffer.pop_back(); return; } - curr_cmd->TextureId = _CmdHeader.TextureId; } @@ -618,6 +661,15 @@ void ImDrawList::PopTextureID() _OnChangedTextureID(); } +// This is used by ImGui::PushFont()/PopFont(). It works because we never use _TextureIdStack[] elsewhere than in PushTextureID()/PopTextureID(). +void ImDrawList::_SetTextureID(ImTextureID texture_id) +{ + if (_CmdHeader.TextureId == texture_id) + return; + _CmdHeader.TextureId = texture_id; + _OnChangedTextureID(); +} + // Reserve space for a number of vertices and indices. // You must finish filling your reserved data before calling PrimReserve() again, as it may reallocate or // submit the intermediate results. PrimUnreserve() can be used to release unused allocations. @@ -1612,7 +1664,7 @@ void ImDrawList::AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const Im PathStroke(col, 0, thickness); } -void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) +void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) { if ((col & IM_COL32_A_MASK) == 0) return; @@ -2208,6 +2260,12 @@ void ImGui::AddDrawListToDrawDataEx(ImDrawData* draw_data, ImVector if (sizeof(ImDrawIdx) == 2) IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices. Read comment above"); + // Resolve callback data pointers + if (draw_list->_CallbacksDataBuf.Size > 0) + for (ImDrawCmd& cmd : draw_list->CmdBuffer) + if (cmd.UserCallback != NULL && cmd.UserCallbackDataOffset != -1 && cmd.UserCallbackDataSize > 0) + cmd.UserCallbackData = draw_list->_CallbacksDataBuf.Data + cmd.UserCallbackDataOffset; + // Add to output list + records state in ImDrawData out_list->push_back(draw_list); draw_data->CmdListsCount++; @@ -2485,13 +2543,15 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) { IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); IM_ASSERT(font_cfg->FontData != NULL && font_cfg->FontDataSize > 0); - IM_ASSERT(font_cfg->SizePixels > 0.0f); + IM_ASSERT(font_cfg->SizePixels > 0.0f && "Is ImFontConfig struct correctly initialized?"); + IM_ASSERT(font_cfg->OversampleH > 0 && font_cfg->OversampleV > 0 && "Is ImFontConfig struct correctly initialized?"); + IM_ASSERT(font_cfg->RasterizerDensity > 0.0f); // Create new font if (!font_cfg->MergeMode) Fonts.push_back(IM_NEW(ImFont)); else - IM_ASSERT(!Fonts.empty() && "Cannot use MergeMode for the first font"); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font. + IM_ASSERT(Fonts.Size > 0 && "Cannot use MergeMode for the first font"); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font. ConfigData.push_back(*font_cfg); ImFontConfig& new_font_cfg = ConfigData.back(); @@ -2504,9 +2564,16 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) memcpy(new_font_cfg.FontData, font_cfg->FontData, (size_t)new_font_cfg.FontDataSize); } + // Round font size + // - We started rounding in 1.90 WIP (18991) as our layout system currently doesn't support non-rounded font size well yet. + // - Note that using io.FontGlobalScale or SetWindowFontScale(), with are legacy-ish, partially supported features, can still lead to unrounded sizes. + // - We may support it better later and remove this rounding. + new_font_cfg.SizePixels = ImTrunc(new_font_cfg.SizePixels); + if (new_font_cfg.DstFont->EllipsisChar == (ImWchar)-1) new_font_cfg.DstFont->EllipsisChar = font_cfg->EllipsisChar; + // Pointers to ConfigData and BuilderData are otherwise dangling ImFontAtlasUpdateConfigDataPointers(this); // Invalidate texture @@ -2518,7 +2585,6 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) // Default font TTF is compressed with stb_compress then base85 encoded (see misc/fonts/binary_to_compressed_c.cpp for encoder) static unsigned int stb_decompress_length(const unsigned char* input); static unsigned int stb_decompress(unsigned char* output, const unsigned char* input, unsigned int length); -static const char* GetDefaultCompressedFontDataTTFBase85(); static unsigned int Decode85Byte(char c) { return c >= '\\' ? c-36 : c-35; } static void Decode85(const unsigned char* src, unsigned char* dst) { @@ -2530,10 +2596,14 @@ static void Decode85(const unsigned char* src, unsigned char* dst) dst += 4; } } +#ifndef IMGUI_DISABLE_DEFAULT_FONT +static const char* GetDefaultCompressedFontDataTTF(int* out_size); +#endif // Load embedded ProggyClean.ttf at size 13, disable oversampling ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) { +#ifndef IMGUI_DISABLE_DEFAULT_FONT ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); if (!font_cfg_template) { @@ -2547,10 +2617,16 @@ ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) font_cfg.EllipsisChar = (ImWchar)0x0085; font_cfg.GlyphOffset.y = 1.0f * IM_TRUNC(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units - const char* ttf_compressed_base85 = GetDefaultCompressedFontDataTTFBase85(); + int ttf_compressed_size = 0; + const char* ttf_compressed = GetDefaultCompressedFontDataTTF(&ttf_compressed_size); const ImWchar* glyph_ranges = font_cfg.GlyphRanges != NULL ? font_cfg.GlyphRanges : GetGlyphRangesDefault(); - ImFont* font = AddFontFromMemoryCompressedBase85TTF(ttf_compressed_base85, font_cfg.SizePixels, &font_cfg, glyph_ranges); + ImFont* font = AddFontFromMemoryCompressedTTF(ttf_compressed, ttf_compressed_size, font_cfg.SizePixels, &font_cfg, glyph_ranges); return font; +#else + IM_ASSERT(0 && "AddFontDefault() disabled in this build."); + IM_UNUSED(font_cfg_template); + return NULL; +#endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT } ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) @@ -2634,6 +2710,7 @@ int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int r.Width = (unsigned short)width; r.Height = (unsigned short)height; r.GlyphID = id; + r.GlyphColored = 0; // Set to 1 manually to mark glyph as colored // FIXME: No official API for that (#8133) r.GlyphAdvanceX = advance_x; r.GlyphOffset = offset; r.Font = font; @@ -2808,8 +2885,9 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2) { // Check for valid range. This may also help detect *some* dangling pointers, because a common - // user error is to setup ImFontConfig::GlyphRanges with a pointer to data that isn't persistent. - IM_ASSERT(src_range[0] <= src_range[1]); + // user error is to setup ImFontConfig::GlyphRanges with a pointer to data that isn't persistent, + // or to forget to zero-terminate the glyph range array. + IM_ASSERT(src_range[0] <= src_range[1] && "Invalid range: is your glyph range array persistent? it is zero-terminated?"); src_tmp.GlyphsHighest = ImMax(src_tmp.GlyphsHighest, (int)src_range[1]); } dst_tmp.SrcCount++; @@ -2869,6 +2947,7 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) int total_surface = 0; int buf_rects_out_n = 0; int buf_packedchars_out_n = 0; + const int pack_padding = atlas->TexGlyphPadding; for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) { ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; @@ -2892,18 +2971,19 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) // Gather the sizes of all rectangles we will need to pack (this loop is based on stbtt_PackFontRangesGatherRects) const float scale = (cfg.SizePixels > 0.0f) ? stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, cfg.SizePixels * cfg.RasterizerDensity) : stbtt_ScaleForMappingEmToPixels(&src_tmp.FontInfo, -cfg.SizePixels * cfg.RasterizerDensity); - const int padding = atlas->TexGlyphPadding; for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++) { int x0, y0, x1, y1; const int glyph_index_in_font = stbtt_FindGlyphIndex(&src_tmp.FontInfo, src_tmp.GlyphsList[glyph_i]); IM_ASSERT(glyph_index_in_font != 0); stbtt_GetGlyphBitmapBoxSubpixel(&src_tmp.FontInfo, glyph_index_in_font, scale * cfg.OversampleH, scale * cfg.OversampleV, 0, 0, &x0, &y0, &x1, &y1); - src_tmp.Rects[glyph_i].w = (stbrp_coord)(x1 - x0 + padding + cfg.OversampleH - 1); - src_tmp.Rects[glyph_i].h = (stbrp_coord)(y1 - y0 + padding + cfg.OversampleV - 1); + src_tmp.Rects[glyph_i].w = (stbrp_coord)(x1 - x0 + pack_padding + cfg.OversampleH - 1); + src_tmp.Rects[glyph_i].h = (stbrp_coord)(y1 - y0 + pack_padding + cfg.OversampleV - 1); total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h; } } + for (int i = 0; i < atlas->CustomRects.Size; i++) + total_surface += (atlas->CustomRects[i].Width + pack_padding) * (atlas->CustomRects[i].Height + pack_padding); // We need a width for the skyline algorithm, any width! // The exact width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. @@ -2919,7 +2999,8 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). const int TEX_HEIGHT_MAX = 1024 * 32; stbtt_pack_context spc = {}; - stbtt_PackBegin(&spc, NULL, atlas->TexWidth, TEX_HEIGHT_MAX, 0, atlas->TexGlyphPadding, NULL); + stbtt_PackBegin(&spc, NULL, atlas->TexWidth, TEX_HEIGHT_MAX, 0, 0, NULL); + spc.padding = atlas->TexGlyphPadding; // Because we mixup stbtt_PackXXX and stbrp_PackXXX there's a bit of a hack here, not passing the value to stbtt_PackBegin() allows us to still pack a TexWidth-1 wide item. (#8107) ImFontAtlasBuildPackCustomRects(atlas, spc.pack_info); // 6. Pack each source font. No rendering yet, we are working with rectangles in an infinitely tall texture at this point. @@ -3065,13 +3146,14 @@ void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opa if (user_rects.Size < 1) { __builtin_unreachable(); } // Workaround for GCC bug if IM_ASSERT() is defined to conditionally throw (see #5343) #endif + const int pack_padding = atlas->TexGlyphPadding; ImVector pack_rects; pack_rects.resize(user_rects.Size); memset(pack_rects.Data, 0, (size_t)pack_rects.size_in_bytes()); for (int i = 0; i < user_rects.Size; i++) { - pack_rects[i].w = user_rects[i].Width; - pack_rects[i].h = user_rects[i].Height; + pack_rects[i].w = user_rects[i].Width + pack_padding; + pack_rects[i].h = user_rects[i].Height + pack_padding; } stbrp_pack_rects(pack_context, &pack_rects[0], pack_rects.Size); for (int i = 0; i < pack_rects.Size; i++) @@ -3079,7 +3161,7 @@ void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opa { user_rects[i].X = (unsigned short)pack_rects[i].x; user_rects[i].Y = (unsigned short)pack_rects[i].y; - IM_ASSERT(pack_rects[i].w == user_rects[i].Width && pack_rects[i].h == user_rects[i].Height); + IM_ASSERT(pack_rects[i].w == user_rects[i].Width + pack_padding && pack_rects[i].h == user_rects[i].Height + pack_padding); atlas->TexHeight = ImMax(atlas->TexHeight, pack_rects[i].y + pack_rects[i].h); } } @@ -3110,35 +3192,35 @@ static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) IM_ASSERT(r->IsPacked()); const int w = atlas->TexWidth; - if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) + if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) { - // Render/copy pixels - IM_ASSERT(r->Width == FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1 && r->Height == FONT_ATLAS_DEFAULT_TEX_DATA_H); - const int x_for_white = r->X; - const int x_for_black = r->X + FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; + // White pixels only + IM_ASSERT(r->Width == 2 && r->Height == 2); + const int offset = (int)r->X + (int)r->Y * w; if (atlas->TexPixelsAlpha8 != NULL) { - ImFontAtlasBuildRender8bppRectFromString(atlas, x_for_white, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.', 0xFF); - ImFontAtlasBuildRender8bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', 0xFF); + atlas->TexPixelsAlpha8[offset] = atlas->TexPixelsAlpha8[offset + 1] = atlas->TexPixelsAlpha8[offset + w] = atlas->TexPixelsAlpha8[offset + w + 1] = 0xFF; } else { - ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_white, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.', IM_COL32_WHITE); - ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', IM_COL32_WHITE); + atlas->TexPixelsRGBA32[offset] = atlas->TexPixelsRGBA32[offset + 1] = atlas->TexPixelsRGBA32[offset + w] = atlas->TexPixelsRGBA32[offset + w + 1] = IM_COL32_WHITE; } } else { - // Render 4 white pixels - IM_ASSERT(r->Width == 2 && r->Height == 2); - const int offset = (int)r->X + (int)r->Y * w; + // White pixels and mouse cursor + IM_ASSERT(r->Width == FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1 && r->Height == FONT_ATLAS_DEFAULT_TEX_DATA_H); + const int x_for_white = r->X; + const int x_for_black = r->X + FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; if (atlas->TexPixelsAlpha8 != NULL) { - atlas->TexPixelsAlpha8[offset] = atlas->TexPixelsAlpha8[offset + 1] = atlas->TexPixelsAlpha8[offset + w] = atlas->TexPixelsAlpha8[offset + w + 1] = 0xFF; + ImFontAtlasBuildRender8bppRectFromString(atlas, x_for_white, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.', 0xFF); + ImFontAtlasBuildRender8bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', 0xFF); } else { - atlas->TexPixelsRGBA32[offset] = atlas->TexPixelsRGBA32[offset + 1] = atlas->TexPixelsRGBA32[offset + w] = atlas->TexPixelsRGBA32[offset + w + 1] = IM_COL32_WHITE; + ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_white, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.', IM_COL32_WHITE); + ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', IM_COL32_WHITE); } } atlas->TexUvWhitePixel = ImVec2((r->X + 0.5f) * atlas->TexUvScale.x, (r->Y + 0.5f) * atlas->TexUvScale.y); @@ -3152,38 +3234,38 @@ static void ImFontAtlasBuildRenderLinesTexData(ImFontAtlas* atlas) // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdLines); IM_ASSERT(r->IsPacked()); - for (unsigned int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row + for (int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row { // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle - unsigned int y = n; - unsigned int line_width = n; - unsigned int pad_left = (r->Width - line_width) / 2; - unsigned int pad_right = r->Width - (pad_left + line_width); + int y = n; + int line_width = n; + int pad_left = (r->Width - line_width) / 2; + int pad_right = r->Width - (pad_left + line_width); // Write each slice IM_ASSERT(pad_left + line_width + pad_right == r->Width && y < r->Height); // Make sure we're inside the texture bounds before we start writing pixels if (atlas->TexPixelsAlpha8 != NULL) { unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r->X + ((r->Y + y) * atlas->TexWidth)]; - for (unsigned int i = 0; i < pad_left; i++) + for (int i = 0; i < pad_left; i++) *(write_ptr + i) = 0x00; - for (unsigned int i = 0; i < line_width; i++) + for (int i = 0; i < line_width; i++) *(write_ptr + pad_left + i) = 0xFF; - for (unsigned int i = 0; i < pad_right; i++) + for (int i = 0; i < pad_right; i++) *(write_ptr + pad_left + line_width + i) = 0x00; } else { unsigned int* write_ptr = &atlas->TexPixelsRGBA32[r->X + ((r->Y + y) * atlas->TexWidth)]; - for (unsigned int i = 0; i < pad_left; i++) + for (int i = 0; i < pad_left; i++) *(write_ptr + i) = IM_COL32(255, 255, 255, 0); - for (unsigned int i = 0; i < line_width; i++) + for (int i = 0; i < line_width; i++) *(write_ptr + pad_left + i) = IM_COL32_WHITE; - for (unsigned int i = 0; i < pad_right; i++) + for (int i = 0; i < pad_right; i++) *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0); } @@ -3198,13 +3280,6 @@ static void ImFontAtlasBuildRenderLinesTexData(ImFontAtlas* atlas) // Note: this is called / shared by both the stb_truetype and the FreeType builder void ImFontAtlasBuildInit(ImFontAtlas* atlas) { - // Round font size - // - We started rounding in 1.90 WIP (18991) as our layout system currently doesn't support non-rounded font size well yet. - // - Note that using io.FontGlobalScale or SetWindowFontScale(), with are legacy-ish, partially supported features, can still lead to unrounded sizes. - // - We may support it better later and remove this rounding. - for (ImFontConfig& cfg : atlas->ConfigData) - cfg.SizePixels = ImTrunc(cfg.SizePixels); - // Register texture region for mouse cursors or standard white pixels if (atlas->PackIdMouseCursors < 0) { @@ -3243,6 +3318,8 @@ void ImFontAtlasBuildFinish(ImFontAtlas* atlas) ImVec2 uv0, uv1; atlas->CalcCustomRectUV(r, &uv0, &uv1); r->Font->AddGlyph(NULL, (ImWchar)r->GlyphID, r->GlyphOffset.x, r->GlyphOffset.y, r->GlyphOffset.x + r->Width, r->GlyphOffset.y + r->Height, uv0.x, uv0.y, uv1.x, uv1.y, r->GlyphAdvanceX); + if (r->GlyphColored) + r->Font->Glyphs.back().Colored = 1; } // Build all fonts lookup tables @@ -3253,6 +3330,10 @@ void ImFontAtlasBuildFinish(ImFontAtlas* atlas) atlas->TexReady = true; } +//------------------------------------------------------------------------- +// [SECTION] ImFontAtlas: glyph ranges helpers +//------------------------------------------------------------------------- + // Retrieve list of range (2 int per range, values are inclusive) const ImWchar* ImFontAtlas::GetGlyphRangesDefault() { @@ -3314,10 +3395,6 @@ static void UnpackAccumulativeOffsetsIntoRanges(int base_codepoint, const short* out_ranges[0] = 0; } -//------------------------------------------------------------------------- -// [SECTION] ImFontAtlas glyph ranges helpers -//------------------------------------------------------------------------- - const ImWchar* ImFontAtlas::GetGlyphRangesChineseSimplifiedCommon() { // Store 2500 regularly used characters for Simplified Chinese. @@ -3596,6 +3673,7 @@ void ImFont::ClearOutputData() DirtyLookupTables = true; Ascent = Descent = 0.0f; MetricsTotalSurface = 0; + memset(Used4kPagesMap, 0, sizeof(Used4kPagesMap)); } static ImWchar FindFirstExistingGlyph(ImFont* font, const ImWchar* candidate_chars, int candidate_chars_count) @@ -3743,8 +3821,9 @@ void ImFont::AddGlyph(const ImFontConfig* cfg, ImWchar codepoint, float x0, floa advance_x += cfg->GlyphExtraSpacing.x; } + int glyph_idx = Glyphs.Size; Glyphs.resize(Glyphs.Size + 1); - ImFontGlyph& glyph = Glyphs.back(); + ImFontGlyph& glyph = Glyphs[glyph_idx]; glyph.Codepoint = (unsigned int)codepoint; glyph.Visible = (x0 != x1) && (y0 != y1); glyph.Colored = false; @@ -3780,7 +3859,8 @@ void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst) IndexAdvanceX[dst] = (src < index_size) ? IndexAdvanceX.Data[src] : 1.0f; } -const ImFontGlyph* ImFont::FindGlyph(ImWchar c) const +// Find glyph, return fallback if missing +const ImFontGlyph* ImFont::FindGlyph(ImWchar c) { if (c >= (size_t)IndexLookup.Size) return FallbackGlyph; @@ -3790,7 +3870,7 @@ const ImFontGlyph* ImFont::FindGlyph(ImWchar c) const return &Glyphs.Data[i]; } -const ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) const +const ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) { if (c >= (size_t)IndexLookup.Size) return NULL; @@ -3800,7 +3880,7 @@ const ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) const return &Glyphs.Data[i]; } -// Wrapping skips upcoming blanks +// Trim trailing space and find beginning of next line static inline const char* CalcWordWrapNextLineStartA(const char* text, const char* text_end) { while (text < text_end && ImCharIsBlankA(*text)) @@ -3810,10 +3890,12 @@ static inline const char* CalcWordWrapNextLineStartA(const char* text, const cha return text; } +#define ImFontGetCharAdvanceX(_FONT, _CH) ((int)(_CH) < (_FONT)->IndexAdvanceX.Size ? (_FONT)->IndexAdvanceX.Data[_CH] : (_FONT)->FallbackAdvanceX) + // Simple word-wrapping for English, not full-featured. Please submit failing cases! // This will return the next location to wrap from. If no wrapping if necessary, this will fast-forward to e.g. text_end. // FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.) -const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const +const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) { // For references, possible wrap point marked with ^ // "aaa bbb, ccc,ddd. eee fff. ggg!" @@ -3862,7 +3944,7 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c } } - const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX.Data[c] : FallbackAdvanceX); + const float char_width = ImFontGetCharAdvanceX(this, c); if (ImCharIsBlankW(c)) { if (inside_word) @@ -3911,7 +3993,7 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c return s; } -ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining) const +ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining) { if (!text_end) text_end = text_begin + strlen(text_begin); // FIXME-OPT: Need to avoid this. @@ -3967,7 +4049,7 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons continue; } - const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX.Data[c] : FallbackAdvanceX) * scale; + const float char_width = ImFontGetCharAdvanceX(this, c) * scale; if (line_width + char_width >= max_width) { s = prev_s; @@ -3990,7 +4072,7 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons } // Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound. -void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c) const +void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c) { const ImFontGlyph* glyph = FindGlyph(c); if (!glyph || !glyph->Visible) @@ -4005,7 +4087,7 @@ void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, Im } // Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound. -void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) const +void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) { if (!text_end) text_end = text_begin + strlen(text_begin); // ImGui:: functions generally already provides a valid text_end, so this is merely to handle direct calls. @@ -4016,9 +4098,9 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im if (y > clip_rect.w) return; - const float start_x = x; const float scale = size / FontSize; const float line_height = FontSize * scale; + const float origin_x = x; const bool word_wrap_enabled = (wrap_width > 0.0f); // Fast-forward to first visible line @@ -4077,11 +4159,11 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im { // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. if (!word_wrap_eol) - word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - start_x)); + word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - origin_x)); if (s >= word_wrap_eol) { - x = start_x; + x = origin_x; y += line_height; if (y > clip_rect.w) break; // break out of main loop @@ -4102,7 +4184,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im { if (c == '\n') { - x = start_x; + x = origin_x; y += line_height; if (y > clip_rect.w) break; // break out of main loop @@ -4198,6 +4280,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im // - RenderArrow() // - RenderBullet() // - RenderCheckMark() +// - RenderArrowDockMenu() // - RenderArrowPointingAt() // - RenderRectFilledRangeH() // - RenderRectFilledWithHole() @@ -4272,6 +4355,14 @@ void ImGui::RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half } } +// This is less wide than RenderArrow() and we use in dock nodes instead of the regular RenderArrow() to denote a change of functionality, +// and because the saved space means that the left-most tab label can stay at exactly the same position as the label of a loose window. +void ImGui::RenderArrowDockMenu(ImDrawList* draw_list, ImVec2 p_min, float sz, ImU32 col) +{ + draw_list->AddRectFilled(p_min + ImVec2(sz * 0.20f, sz * 0.15f), p_min + ImVec2(sz * 0.80f, sz * 0.30f), col); + RenderArrowPointingAt(draw_list, p_min + ImVec2(sz * 0.50f, sz * 0.85f), ImVec2(sz * 0.30f, sz * 0.40f), ImGuiDir_Down, col); +} + static inline float ImAcos01(float x) { if (x <= 0.0f) return IM_PI * 0.5f; @@ -4357,6 +4448,17 @@ void ImGui::RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, if (fill_R && fill_D) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Max.y), ImVec2(outer.Max.x, outer.Max.y), col, rounding, ImDrawFlags_RoundCornersBottomRight); } +ImDrawFlags ImGui::CalcRoundingFlagsForRectInRect(const ImRect& r_in, const ImRect& r_outer, float threshold) +{ + bool round_l = r_in.Min.x <= r_outer.Min.x + threshold; + bool round_r = r_in.Max.x >= r_outer.Max.x - threshold; + bool round_t = r_in.Min.y <= r_outer.Min.y + threshold; + bool round_b = r_in.Max.y >= r_outer.Max.y - threshold; + return ImDrawFlags_RoundCornersNone + | ((round_t && round_l) ? ImDrawFlags_RoundCornersTopLeft : 0) | ((round_t && round_r) ? ImDrawFlags_RoundCornersTopRight : 0) + | ((round_b && round_l) ? ImDrawFlags_RoundCornersBottomLeft : 0) | ((round_b && round_r) ? ImDrawFlags_RoundCornersBottomRight : 0); +} + // Helper for ColorPicker4() // NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that. // Spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding altogether. @@ -4525,101 +4627,187 @@ static unsigned int stb_decompress(unsigned char *output, const unsigned char *i // MIT license (see License.txt in http://www.proggyfonts.net/index.php?menu=download) // Download and more information at http://www.proggyfonts.net or http://upperboundsinteractive.com/fonts.php //----------------------------------------------------------------------------- + +#ifndef IMGUI_DISABLE_DEFAULT_FONT + // File: 'ProggyClean.ttf' (41208 bytes) -// Exported using misc/fonts/binary_to_compressed_c.cpp (with compression + base85 string encoding). -// The purpose of encoding as base85 instead of "0x00,0x01,..." style is only save on _source code_ size. -//----------------------------------------------------------------------------- -static const char proggy_clean_ttf_compressed_data_base85[11980 + 1] = - "7])#######hV0qs'/###[),##/l:$#Q6>##5[n42>c-TH`->>#/e>11NNV=Bv(*:.F?uu#(gRU.o0XGH`$vhLG1hxt9?W`#,5LsCp#-i>.r$<$6pD>Lb';9Crc6tgXmKVeU2cD4Eo3R/" - "2*>]b(MC;$jPfY.;h^`IWM9Qo#t'X#(v#Y9w0#1D$CIf;W'#pWUPXOuxXuU(H9M(1=Ke$$'5F%)]0^#0X@U.a$FBjVQTSDgEKnIS7EM9>ZY9w0#L;>>#Mx&4Mvt//L[MkA#W@lK.N'[0#7RL_&#w+F%HtG9M#XL`N&.,GM4Pg;--VsM.M0rJfLH2eTM`*oJMHRC`N" - "kfimM2J,W-jXS:)r0wK#@Fge$U>`w'N7G#$#fB#$E^$#:9:hk+eOe--6x)F7*E%?76%^GMHePW-Z5l'&GiF#$956:rS?dA#fiK:)Yr+`�j@'DbG&#^$PG.Ll+DNa&VZ>1i%h1S9u5o@YaaW$e+bROPOpxTO7Stwi1::iB1q)C_=dV26J;2,]7op$]uQr@_V7$q^%lQwtuHY]=DX,n3L#0PHDO4f9>dC@O>HBuKPpP*E,N+b3L#lpR/MrTEH.IAQk.a>D[.e;mc." - "x]Ip.PH^'/aqUO/$1WxLoW0[iLAw=4h(9.`G" - "CRUxHPeR`5Mjol(dUWxZa(>STrPkrJiWx`5U7F#.g*jrohGg`cg:lSTvEY/EV_7H4Q9[Z%cnv;JQYZ5q.l7Zeas:HOIZOB?Ggv:[7MI2k).'2($5FNP&EQ(,)" - "U]W]+fh18.vsai00);D3@4ku5P?DP8aJt+;qUM]=+b'8@;mViBKx0DE[-auGl8:PJ&Dj+M6OC]O^((##]`0i)drT;-7X`=-H3[igUnPG-NZlo.#k@h#=Ork$m>a>$-?Tm$UV(?#P6YY#" - "'/###xe7q.73rI3*pP/$1>s9)W,JrM7SN]'/4C#v$U`0#V.[0>xQsH$fEmPMgY2u7Kh(G%siIfLSoS+MK2eTM$=5,M8p`A.;_R%#u[K#$x4AG8.kK/HSB==-'Ie/QTtG?-.*^N-4B/ZM" - "_3YlQC7(p7q)&](`6_c)$/*JL(L-^(]$wIM`dPtOdGA,U3:w2M-0+WomX2u7lqM2iEumMTcsF?-aT=Z-97UEnXglEn1K-bnEO`gu" - "Ft(c%=;Am_Qs@jLooI&NX;]0#j4#F14;gl8-GQpgwhrq8'=l_f-b49'UOqkLu7-##oDY2L(te+Mch&gLYtJ,MEtJfLh'x'M=$CS-ZZ%P]8bZ>#S?YY#%Q&q'3^Fw&?D)UDNrocM3A76/" - "/oL?#h7gl85[qW/NDOk%16ij;+:1a'iNIdb-ou8.P*w,v5#EI$TWS>Pot-R*H'-SEpA:g)f+O$%%`kA#G=8RMmG1&O`>to8bC]T&$,n.LoO>29sp3dt-52U%VM#q7'DHpg+#Z9%H[Ket`e;)f#Km8&+DC$I46>#Kr]]u-[=99tts1.qb#q72g1WJO81q+eN'03'eM>&1XxY-caEnO" - "j%2n8)),?ILR5^.Ibn<-X-Mq7[a82Lq:F&#ce+S9wsCK*x`569E8ew'He]h:sI[2LM$[guka3ZRd6:t%IG:;$%YiJ:Nq=?eAw;/:nnDq0(CYcMpG)qLN4$##&J-XTt,%OVU4)S1+R-#dg0/Nn?Ku1^0f$B*P:Rowwm-`0PKjYDDM'3]d39VZHEl4,.j']Pk-M.h^&:0FACm$maq-&sgw0t7/6(^xtk%" - "LuH88Fj-ekm>GA#_>568x6(OFRl-IZp`&b,_P'$MhLbxfc$mj`,O;&%W2m`Zh:/)Uetw:aJ%]K9h:TcF]u_-Sj9,VK3M.*'&0D[Ca]J9gp8,kAW]" - "%(?A%R$f<->Zts'^kn=-^@c4%-pY6qI%J%1IGxfLU9CP8cbPlXv);C=b),<2mOvP8up,UVf3839acAWAW-W?#ao/^#%KYo8fRULNd2.>%m]UK:n%r$'sw]J;5pAoO_#2mO3n,'=H5(et" - "Hg*`+RLgv>=4U8guD$I%D:W>-r5V*%j*W:Kvej.Lp$'?;++O'>()jLR-^u68PHm8ZFWe+ej8h:9r6L*0//c&iH&R8pRbA#Kjm%upV1g:" - "a_#Ur7FuA#(tRh#.Y5K+@?3<-8m0$PEn;J:rh6?I6uG<-`wMU'ircp0LaE_OtlMb&1#6T.#FDKu#1Lw%u%+GM+X'e?YLfjM[VO0MbuFp7;>Q&#WIo)0@F%q7c#4XAXN-U&VBpqB>0ie&jhZ[?iLR@@_AvA-iQC(=ksRZRVp7`.=+NpBC%rh&3]R:8XDmE5^V8O(x<-+k?'(^](H.aREZSi,#1:[IXaZFOm<-ui#qUq2$##Ri;u75OK#(RtaW-K-F`S+cF]uN`-KMQ%rP/Xri.LRcB##=YL3BgM/3M" - "D?@f&1'BW-)Ju#bmmWCMkk&#TR`C,5d>g)F;t,4:@_l8G/5h4vUd%&%950:VXD'QdWoY-F$BtUwmfe$YqL'8(PWX(" - "P?^@Po3$##`MSs?DWBZ/S>+4%>fX,VWv/w'KD`LP5IbH;rTV>n3cEK8U#bX]l-/V+^lj3;vlMb&[5YQ8#pekX9JP3XUC72L,,?+Ni&co7ApnO*5NK,((W-i:$,kp'UDAO(G0Sq7MVjJs" - "bIu)'Z,*[>br5fX^:FPAWr-m2KgLQ_nN6'8uTGT5g)uLv:873UpTLgH+#FgpH'_o1780Ph8KmxQJ8#H72L4@768@Tm&Q" - "h4CB/5OvmA&,Q&QbUoi$a_%3M01H)4x7I^&KQVgtFnV+;[Pc>[m4k//,]1?#`VY[Jr*3&&slRfLiVZJ:]?=K3Sw=[$=uRB?3xk48@aege0jT6'N#(q%.O=?2S]u*(m<-" - "V8J'(1)G][68hW$5'q[GC&5j`TE?m'esFGNRM)j,ffZ?-qx8;->g4t*:CIP/[Qap7/9'#(1sao7w-.qNUdkJ)tCF&#B^;xGvn2r9FEPFFFcL@.iFNkTve$m%#QvQS8U@)2Z+3K:AKM5i" - "sZ88+dKQ)W6>J%CL`.d*(B`-n8D9oK-XV1q['-5k'cAZ69e;D_?$ZPP&s^+7])$*$#@QYi9,5P r+$%CE=68>K8r0=dSC%%(@p7" - ".m7jilQ02'0-VWAgTlGW'b)Tq7VT9q^*^$$.:&N@@" - "$&)WHtPm*5_rO0&e%K&#-30j(E4#'Zb.o/(Tpm$>K'f@[PvFl,hfINTNU6u'0pao7%XUp9]5.>%h`8_=VYbxuel.NTSsJfLacFu3B'lQSu/m6-Oqem8T+oE--$0a/k]uj9EwsG>%veR*" - "hv^BFpQj:K'#SJ,sB-'#](j.Lg92rTw-*n%@/;39rrJF,l#qV%OrtBeC6/,;qB3ebNW[?,Hqj2L.1NP&GjUR=1D8QaS3Up&@*9wP?+lo7b?@%'k4`p0Z$22%K3+iCZj?XJN4Nm&+YF]u" - "@-W$U%VEQ/,,>>#)D#%8cY#YZ?=,`Wdxu/ae&#" - "w6)R89tI#6@s'(6Bf7a&?S=^ZI_kS&ai`&=tE72L_D,;^R)7[$so8lKN%5/$(vdfq7+ebA#" - "u1p]ovUKW&Y%q]'>$1@-[xfn$7ZTp7mM,G,Ko7a&Gu%G[RMxJs[0MM%wci.LFDK)(%:_i2B5CsR8&9Z&#=mPEnm0f`<&c)QL5uJ#%u%lJj+D-r;BoFDoS97h5g)E#o:&S4weDF,9^Hoe`h*L+_a*NrLW-1pG_&2UdB8" - "6e%B/:=>)N4xeW.*wft-;$'58-ESqr#U`'6AQ]m&6/`Z>#S?YY#Vc;r7U2&326d=w&H####?TZ`*4?&.MK?LP8Vxg>$[QXc%QJv92.(Db*B)gb*BM9dM*hJMAo*c&#" - "b0v=Pjer]$gG&JXDf->'StvU7505l9$AFvgYRI^&<^b68?j#q9QX4SM'RO#&sL1IM.rJfLUAj221]d##DW=m83u5;'bYx,*Sl0hL(W;;$doB&O/TQ:(Z^xBdLjLV#*8U_72Lh+2Q8Cj0i:6hp&$C/:p(HK>T8Y[gHQ4`4)'$Ab(Nof%V'8hL&#SfD07&6D@M.*J:;$-rv29'M]8qMv-tLp,'886iaC=Hb*YJoKJ,(j%K=H`K.v9HggqBIiZu'QvBT.#=)0ukruV&.)3=(^1`o*Pj4<-#MJ+gLq9-##@HuZPN0]u:h7.T..G:;$/Usj(T7`Q8tT72LnYl<-qx8;-HV7Q-&Xdx%1a,hC=0u+HlsV>nuIQL-5" - "_>@kXQtMacfD.m-VAb8;IReM3$wf0''hra*so568'Ip&vRs849'MRYSp%:t:h5qSgwpEr$B>Q,;s(C#$)`svQuF$##-D,##,g68@2[T;.XSdN9Qe)rpt._K-#5wF)sP'##p#C0c%-Gb%" - "hd+<-j'Ai*x&&HMkT]C'OSl##5RG[JXaHN;d'uA#x._U;.`PU@(Z3dt4r152@:v,'R.Sj'w#0<-;kPI)FfJ&#AYJ&#//)>-k=m=*XnK$>=)72L]0I%>.G690a:$##<,);?;72#?x9+d;" - "^V'9;jY@;)br#q^YQpx:X#Te$Z^'=-=bGhLf:D6&bNwZ9-ZD#n^9HhLMr5G;']d&6'wYmTFmLq9wI>P(9mI[>kC-ekLC/R&CH+s'B;K-M6$EB%is00:" - "+A4[7xks.LrNk0&E)wILYF@2L'0Nb$+pv<(2.768/FrY&h$^3i&@+G%JT'<-,v`3;_)I9M^AE]CN?Cl2AZg+%4iTpT3$U4O]GKx'm9)b@p7YsvK3w^YR-" - "CdQ*:Ir<($u&)#(&?L9Rg3H)4fiEp^iI9O8KnTj,]H?D*r7'M;PwZ9K0E^k&-cpI;.p/6_vwoFMV<->#%Xi.LxVnrU(4&8/P+:hLSKj$#U%]49t'I:rgMi'FL@a:0Y-uA[39',(vbma*" - "hU%<-SRF`Tt:542R_VV$p@[p8DV[A,?1839FWdFTi1O*H&#(AL8[_P%.M>v^-))qOT*F5Cq0`Ye%+$B6i:7@0IXSsDiWP,##P`%/L-" - "S(qw%sf/@%#B6;/U7K]uZbi^Oc^2n%t<)'mEVE''n`WnJra$^TKvX5B>;_aSEK',(hwa0:i4G?.Bci.(X[?b*($,=-n<.Q%`(X=?+@Am*Js0&=3bh8K]mL69=Lb,OcZV/);TTm8VI;?%OtJ<(b4mq7M6:u?KRdFl*:xP?Yb.5)%w_I?7uk5JC+FS(m#i'k.'a0i)9<7b'fs'59hq$*5Uhv##pi^8+hIEBF`nvo`;'l0.^S1<-wUK2/Coh58KKhLj" - "M=SO*rfO`+qC`W-On.=AJ56>>i2@2LH6A:&5q`?9I3@@'04&p2/LVa*T-4<-i3;M9UvZd+N7>b*eIwg:CC)c<>nO&#$(>.Z-I&J(Q0Hd5Q%7Co-b`-cP)hI;*_F]u`Rb[.j8_Q/<&>uu+VsH$sM9TA%?)(vmJ80),P7E>)tjD%2L=-t#fK[%`v=Q8WlA2);Sa" - ">gXm8YB`1d@K#n]76-a$U,mF%Ul:#/'xoFM9QX-$.QN'>" - "[%$Z$uF6pA6Ki2O5:8w*vP1<-1`[G,)-m#>0`P&#eb#.3i)rtB61(o'$?X3B2Qft^ae_5tKL9MUe9b*sLEQ95C&`=G?@Mj=wh*'3E>=-<)Gt*Iw)'QG:`@I" - "wOf7&]1i'S01B+Ev/Nac#9S;=;YQpg_6U`*kVY39xK,[/6Aj7:'1Bm-_1EYfa1+o&o4hp7KN_Q(OlIo@S%;jVdn0'1h19w,WQhLI)3S#f$2(eb,jr*b;3Vw]*7NH%$c4Vs,eD9>XW8?N]o+(*pgC%/72LV-uW%iewS8W6m2rtCpo'RS1R84=@paTKt)>=%&1[)*vp'u+x,VrwN;&]kuO9JDbg=pO$J*.jVe;u'm0dr9l,<*wMK*Oe=g8lV_KEBFkO'oU]^=[-792#ok,)" - "i]lR8qQ2oA8wcRCZ^7w/Njh;?.stX?Q1>S1q4Bn$)K1<-rGdO'$Wr.Lc.CG)$/*JL4tNR/,SVO3,aUw'DJN:)Ss;wGn9A32ijw%FL+Z0Fn.U9;reSq)bmI32U==5ALuG&#Vf1398/pVo" - "1*c-(aY168o<`JsSbk-,1N;$>0:OUas(3:8Z972LSfF8eb=c-;>SPw7.6hn3m`9^Xkn(r.qS[0;T%&Qc=+STRxX'q1BNk3&*eu2;&8q$&x>Q#Q7^Tf+6<(d%ZVmj2bDi%.3L2n+4W'$P" - "iDDG)g,r%+?,$@?uou5tSe2aN_AQU*'IAO" - "URQ##V^Fv-XFbGM7Fl(N<3DhLGF%q.1rC$#:T__&Pi68%0xi_&[qFJ(77j_&JWoF.V735&T,[R*:xFR*K5>>#`bW-?4Ne_&6Ne_&6Ne_&n`kr-#GJcM6X;uM6X;uM(.a..^2TkL%oR(#" - ";u.T%fAr%4tJ8&><1=GHZ_+m9/#H1F^R#SC#*N=BA9(D?v[UiFY>>^8p,KKF.W]L29uLkLlu/+4T" - "w$)F./^n3+rlo+DB;5sIYGNk+i1t-69Jg--0pao7Sm#K)pdHW&;LuDNH@H>#/X-TI(;P>#,Gc>#0Su>#4`1?#8lC?#xL$#B.`$#F:r$#JF.%#NR@%#R_R%#Vke%#Zww%#_-4^Rh%Sflr-k'MS.o?.5/sWel/wpEM0%3'/1)K^f1-d>G21&v(35>V`39V7A4=onx4" - "A1OY5EI0;6Ibgr6M$HS7Q<)58C5w,;WoA*#[%T*#`1g*#d=#+#hI5+#lUG+#pbY+#tnl+#x$),#&1;,#*=M,#.I`,#2Ur,#6b.-#;w[H#iQtA#m^0B#qjBB#uvTB##-hB#'9$C#+E6C#" - "/QHC#3^ZC#7jmC#;v)D#?,)4kMYD4lVu`4m`:&5niUA5@(A5BA1]PBB:xlBCC=2CDLXMCEUtiCf&0g2'tN?PGT4CPGT4CPGT4CPGT4CPGT4CPGT4CPGT4CP" - "GT4CPGT4CPGT4CPGT4CPGT4CPGT4CP-qekC`.9kEg^+F$kwViFJTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5o,^<-28ZI'O?;xp" - "O?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xp;7q-#lLYI:xvD=#"; - -static const char* GetDefaultCompressedFontDataTTFBase85() -{ - return proggy_clean_ttf_compressed_data_base85; +// Exported using binary_to_compressed_c.exe -u8 "ProggyClean.ttf" proggy_clean_ttf +static const unsigned int proggy_clean_ttf_compressed_size = 9583; +static const unsigned char proggy_clean_ttf_compressed_data[9583] = +{ + 87,188,0,0,0,0,0,0,0,0,160,248,0,4,0,0,55,0,1,0,0,0,12,0,128,0,3,0,64,79,83,47,50,136,235,116,144,0,0,1,72,130,21,44,78,99,109,97,112,2,18,35,117,0,0,3,160,130,19,36,82,99,118,116, + 32,130,23,130,2,33,4,252,130,4,56,2,103,108,121,102,18,175,137,86,0,0,7,4,0,0,146,128,104,101,97,100,215,145,102,211,130,27,32,204,130,3,33,54,104,130,16,39,8,66,1,195,0,0,1,4,130, + 15,59,36,104,109,116,120,138,0,126,128,0,0,1,152,0,0,2,6,108,111,99,97,140,115,176,216,0,0,5,130,30,41,2,4,109,97,120,112,1,174,0,218,130,31,32,40,130,16,44,32,110,97,109,101,37,89, + 187,150,0,0,153,132,130,19,44,158,112,111,115,116,166,172,131,239,0,0,155,36,130,51,44,210,112,114,101,112,105,2,1,18,0,0,4,244,130,47,32,8,132,203,46,1,0,0,60,85,233,213,95,15,60, + 245,0,3,8,0,131,0,34,183,103,119,130,63,43,0,0,189,146,166,215,0,0,254,128,3,128,131,111,130,241,33,2,0,133,0,32,1,130,65,38,192,254,64,0,0,3,128,131,16,130,5,32,1,131,7,138,3,33,2, + 0,130,17,36,1,1,0,144,0,130,121,130,23,38,2,0,8,0,64,0,10,130,9,32,118,130,9,130,6,32,0,130,59,33,1,144,131,200,35,2,188,2,138,130,16,32,143,133,7,37,1,197,0,50,2,0,131,0,33,4,9,131, + 5,145,3,43,65,108,116,115,0,64,0,0,32,172,8,0,131,0,35,5,0,1,128,131,77,131,3,33,3,128,191,1,33,1,128,130,184,35,0,0,128,0,130,3,131,11,32,1,130,7,33,0,128,131,1,32,1,136,9,32,0,132, + 15,135,5,32,1,131,13,135,27,144,35,32,1,149,25,131,21,32,0,130,0,32,128,132,103,130,35,132,39,32,0,136,45,136,97,133,17,130,5,33,0,0,136,19,34,0,128,1,133,13,133,5,32,128,130,15,132, + 131,32,3,130,5,32,3,132,27,144,71,32,0,133,27,130,29,130,31,136,29,131,63,131,3,65,63,5,132,5,132,205,130,9,33,0,0,131,9,137,119,32,3,132,19,138,243,130,55,32,1,132,35,135,19,131,201, + 136,11,132,143,137,13,130,41,32,0,131,3,144,35,33,128,0,135,1,131,223,131,3,141,17,134,13,136,63,134,15,136,53,143,15,130,96,33,0,3,131,4,130,3,34,28,0,1,130,5,34,0,0,76,130,17,131, + 9,36,28,0,4,0,48,130,17,46,8,0,8,0,2,0,0,0,127,0,255,32,172,255,255,130,9,34,0,0,129,132,9,130,102,33,223,213,134,53,132,22,33,1,6,132,6,64,4,215,32,129,165,216,39,177,0,1,141,184, + 1,255,133,134,45,33,198,0,193,1,8,190,244,1,28,1,158,2,20,2,136,2,252,3,20,3,88,3,156,3,222,4,20,4,50,4,80,4,98,4,162,5,22,5,102,5,188,6,18,6,116,6,214,7,56,7,126,7,236,8,78,8,108, + 8,150,8,208,9,16,9,74,9,136,10,22,10,128,11,4,11,86,11,200,12,46,12,130,12,234,13,94,13,164,13,234,14,80,14,150,15,40,15,176,16,18,16,116,16,224,17,82,17,182,18,4,18,110,18,196,19, + 76,19,172,19,246,20,88,20,174,20,234,21,64,21,128,21,166,21,184,22,18,22,126,22,198,23,52,23,142,23,224,24,86,24,186,24,238,25,54,25,150,25,212,26,72,26,156,26,240,27,92,27,200,28, + 4,28,76,28,150,28,234,29,42,29,146,29,210,30,64,30,142,30,224,31,36,31,118,31,166,31,166,32,16,130,1,52,46,32,138,32,178,32,200,33,20,33,116,33,152,33,238,34,98,34,134,35,12,130,1, + 33,128,35,131,1,60,152,35,176,35,216,36,0,36,74,36,104,36,144,36,174,37,6,37,96,37,130,37,248,37,248,38,88,38,170,130,1,8,190,216,39,64,39,154,40,10,40,104,40,168,41,14,41,32,41,184, + 41,248,42,54,42,96,42,96,43,2,43,42,43,94,43,172,43,230,44,32,44,52,44,154,45,40,45,92,45,120,45,170,45,232,46,38,46,166,47,38,47,182,47,244,48,94,48,200,49,62,49,180,50,30,50,158, + 51,30,51,130,51,238,52,92,52,206,53,58,53,134,53,212,54,38,54,114,54,230,55,118,55,216,56,58,56,166,57,18,57,116,57,174,58,46,58,154,59,6,59,124,59,232,60,58,60,150,61,34,61,134,61, + 236,62,86,62,198,63,42,63,154,64,18,64,106,64,208,65,54,65,162,66,8,66,64,66,122,66,184,66,240,67,98,67,204,68,42,68,138,68,238,69,88,69,182,69,226,70,84,70,180,71,20,71,122,71,218, + 72,84,72,198,73,64,0,36,70,21,8,8,77,3,0,7,0,11,0,15,0,19,0,23,0,27,0,31,0,35,0,39,0,43,0,47,0,51,0,55,0,59,0,63,0,67,0,71,0,75,0,79,0,83,0,87,0,91,0,95,0,99,0,103,0,107,0,111,0,115, + 0,119,0,123,0,127,0,131,0,135,0,139,0,143,0,0,17,53,51,21,49,150,3,32,5,130,23,32,33,130,3,211,7,151,115,32,128,133,0,37,252,128,128,2,128,128,190,5,133,74,32,4,133,6,206,5,42,0,7, + 1,128,0,0,2,0,4,0,0,65,139,13,37,0,1,53,51,21,7,146,3,32,3,130,19,32,1,141,133,32,3,141,14,131,13,38,255,0,128,128,0,6,1,130,84,35,2,128,4,128,140,91,132,89,32,51,65,143,6,139,7,33, + 1,0,130,57,32,254,130,3,32,128,132,4,32,4,131,14,138,89,35,0,0,24,0,130,0,33,3,128,144,171,66,55,33,148,115,65,187,19,32,5,130,151,143,155,163,39,32,1,136,182,32,253,134,178,132,7, + 132,200,145,17,32,3,65,48,17,165,17,39,0,0,21,0,128,255,128,3,65,175,17,65,3,27,132,253,131,217,139,201,155,233,155,27,131,67,131,31,130,241,33,255,0,131,181,137,232,132,15,132,4,138, + 247,34,255,0,128,179,238,32,0,130,0,32,20,65,239,48,33,0,19,67,235,10,32,51,65,203,14,65,215,11,32,7,154,27,135,39,32,33,130,35,33,128,128,130,231,32,253,132,231,32,128,132,232,34, + 128,128,254,133,13,136,8,32,253,65,186,5,130,36,130,42,176,234,133,231,34,128,0,0,66,215,44,33,0,1,68,235,6,68,211,19,32,49,68,239,14,139,207,139,47,66,13,7,32,51,130,47,33,1,0,130, + 207,35,128,128,1,0,131,222,131,5,130,212,130,6,131,212,32,0,130,10,133,220,130,233,130,226,32,254,133,255,178,233,39,3,1,128,3,0,2,0,4,68,15,7,68,99,12,130,89,130,104,33,128,4,133, + 93,130,10,38,0,0,11,1,0,255,0,68,63,16,70,39,9,66,215,8,32,7,68,77,6,68,175,14,32,29,68,195,6,132,7,35,2,0,128,255,131,91,132,4,65,178,5,141,111,67,129,23,165,135,140,107,142,135,33, + 21,5,69,71,6,131,7,33,1,0,140,104,132,142,130,4,137,247,140,30,68,255,12,39,11,0,128,0,128,3,0,3,69,171,15,67,251,7,65,15,8,66,249,11,65,229,7,67,211,7,66,13,7,35,1,128,128,254,133, + 93,32,254,131,145,132,4,132,18,32,2,151,128,130,23,34,0,0,9,154,131,65,207,8,68,107,15,68,51,7,32,7,70,59,7,135,121,130,82,32,128,151,111,41,0,0,4,0,128,255,0,1,128,1,137,239,33,0, + 37,70,145,10,65,77,10,65,212,14,37,0,0,0,5,0,128,66,109,5,70,123,10,33,0,19,72,33,18,133,237,70,209,11,33,0,2,130,113,137,119,136,115,33,1,0,133,43,130,5,34,0,0,10,69,135,6,70,219, + 13,66,155,7,65,9,12,66,157,11,66,9,11,32,7,130,141,132,252,66,151,9,137,9,66,15,30,36,0,20,0,128,0,130,218,71,11,42,68,51,8,65,141,7,73,19,15,69,47,23,143,39,66,81,7,32,1,66,55,6,34, + 1,128,128,68,25,5,69,32,6,137,6,136,25,32,254,131,42,32,3,66,88,26,148,26,32,0,130,0,32,14,164,231,70,225,12,66,233,7,67,133,19,71,203,15,130,161,32,255,130,155,32,254,139,127,134, + 12,164,174,33,0,15,164,159,33,59,0,65,125,20,66,25,7,32,5,68,191,6,66,29,7,144,165,65,105,9,35,128,128,255,0,137,2,133,182,164,169,33,128,128,197,171,130,155,68,235,7,32,21,70,77,19, + 66,21,10,68,97,8,66,30,5,66,4,43,34,0,17,0,71,19,41,65,253,20,71,25,23,65,91,15,65,115,7,34,2,128,128,66,9,8,130,169,33,1,0,66,212,13,132,28,72,201,43,35,0,0,0,18,66,27,38,76,231,5, + 68,157,20,135,157,32,7,68,185,13,65,129,28,66,20,5,32,253,66,210,11,65,128,49,133,61,32,0,65,135,6,74,111,37,72,149,12,66,203,19,65,147,19,68,93,7,68,85,8,76,4,5,33,255,0,133,129,34, + 254,0,128,68,69,8,181,197,34,0,0,12,65,135,32,65,123,20,69,183,27,133,156,66,50,5,72,87,10,67,137,32,33,0,19,160,139,78,251,13,68,55,20,67,119,19,65,91,36,69,177,15,32,254,143,16,65, + 98,53,32,128,130,0,32,0,66,43,54,70,141,23,66,23,15,131,39,69,47,11,131,15,70,129,19,74,161,9,36,128,255,0,128,254,130,153,65,148,32,67,41,9,34,0,0,4,79,15,5,73,99,10,71,203,8,32,3, + 72,123,6,72,43,8,32,2,133,56,131,99,130,9,34,0,0,6,72,175,5,73,159,14,144,63,135,197,132,189,133,66,33,255,0,73,6,7,70,137,12,35,0,0,0,10,130,3,73,243,25,67,113,12,65,73,7,69,161,7, + 138,7,37,21,2,0,128,128,254,134,3,73,116,27,33,128,128,130,111,39,12,0,128,1,0,3,128,2,72,219,21,35,43,0,47,0,67,47,20,130,111,33,21,1,68,167,13,81,147,8,133,230,32,128,77,73,6,32, + 128,131,142,134,18,130,6,32,255,75,18,12,131,243,37,128,0,128,3,128,3,74,231,21,135,123,32,29,134,107,135,7,32,21,74,117,7,135,7,134,96,135,246,74,103,23,132,242,33,0,10,67,151,28, + 67,133,20,66,141,11,131,11,32,3,77,71,6,32,128,130,113,32,1,81,4,6,134,218,66,130,24,131,31,34,0,26,0,130,0,77,255,44,83,15,11,148,155,68,13,7,32,49,78,231,18,79,7,11,73,243,11,32, + 33,65,187,10,130,63,65,87,8,73,239,19,35,0,128,1,0,131,226,32,252,65,100,6,32,128,139,8,33,1,0,130,21,32,253,72,155,44,73,255,20,32,128,71,67,8,81,243,39,67,15,20,74,191,23,68,121, + 27,32,1,66,150,6,32,254,79,19,11,131,214,32,128,130,215,37,2,0,128,253,0,128,136,5,65,220,24,147,212,130,210,33,0,24,72,219,42,84,255,13,67,119,16,69,245,19,72,225,19,65,3,15,69,93, + 19,131,55,132,178,71,115,14,81,228,6,142,245,33,253,0,132,43,172,252,65,16,11,75,219,8,65,219,31,66,223,24,75,223,10,33,29,1,80,243,10,66,175,8,131,110,134,203,133,172,130,16,70,30, + 7,164,183,130,163,32,20,65,171,48,65,163,36,65,143,23,65,151,19,65,147,13,65,134,17,133,17,130,216,67,114,5,164,217,65,137,12,72,147,48,79,71,19,74,169,22,80,251,8,65,173,7,66,157, + 15,74,173,15,32,254,65,170,8,71,186,45,72,131,6,77,143,40,187,195,152,179,65,123,38,68,215,57,68,179,15,65,85,7,69,187,14,32,21,66,95,15,67,19,25,32,1,83,223,6,32,2,76,240,7,77,166, + 43,65,8,5,130,206,32,0,67,39,54,143,167,66,255,19,82,193,11,151,47,85,171,5,67,27,17,132,160,69,172,11,69,184,56,66,95,6,33,12,1,130,237,32,2,68,179,27,68,175,16,80,135,15,72,55,7, + 71,87,12,73,3,12,132,12,66,75,32,76,215,5,169,139,147,135,148,139,81,12,12,81,185,36,75,251,7,65,23,27,76,215,9,87,165,12,65,209,15,72,157,7,65,245,31,32,128,71,128,6,32,1,82,125,5, + 34,0,128,254,131,169,32,254,131,187,71,180,9,132,27,32,2,88,129,44,32,0,78,47,40,65,79,23,79,171,14,32,21,71,87,8,72,15,14,65,224,33,130,139,74,27,62,93,23,7,68,31,7,75,27,7,139,15, + 74,3,7,74,23,27,65,165,11,65,177,15,67,123,5,32,1,130,221,32,252,71,96,5,74,12,12,133,244,130,25,34,1,0,128,130,2,139,8,93,26,8,65,9,32,65,57,14,140,14,32,0,73,79,67,68,119,11,135, + 11,32,51,90,75,14,139,247,65,43,7,131,19,139,11,69,159,11,65,247,6,36,1,128,128,253,0,90,71,9,33,1,0,132,14,32,128,89,93,14,69,133,6,130,44,131,30,131,6,65,20,56,33,0,16,72,179,40, + 75,47,12,65,215,19,74,95,19,65,43,11,131,168,67,110,5,75,23,17,69,106,6,75,65,5,71,204,43,32,0,80,75,47,71,203,15,159,181,68,91,11,67,197,7,73,101,13,68,85,6,33,128,128,130,214,130, + 25,32,254,74,236,48,130,194,37,0,18,0,128,255,128,77,215,40,65,139,64,32,51,80,159,10,65,147,39,130,219,84,212,43,130,46,75,19,97,74,33,11,65,201,23,65,173,31,33,1,0,79,133,6,66,150, + 5,67,75,48,85,187,6,70,207,37,32,71,87,221,13,73,163,14,80,167,15,132,15,83,193,19,82,209,8,78,99,9,72,190,11,77,110,49,89,63,5,80,91,35,99,63,32,70,235,23,81,99,10,69,148,10,65,110, + 36,32,0,65,99,47,95,219,11,68,171,51,66,87,7,72,57,7,74,45,17,143,17,65,114,50,33,14,0,65,111,40,159,195,98,135,15,35,7,53,51,21,100,78,9,95,146,16,32,254,82,114,6,32,128,67,208,37, + 130,166,99,79,58,32,17,96,99,14,72,31,19,72,87,31,82,155,7,67,47,14,32,21,131,75,134,231,72,51,17,72,78,8,133,8,80,133,6,33,253,128,88,37,9,66,124,36,72,65,12,134,12,71,55,43,66,139, + 27,85,135,10,91,33,12,65,35,11,66,131,11,71,32,8,90,127,6,130,244,71,76,11,168,207,33,0,12,66,123,32,32,0,65,183,15,68,135,11,66,111,7,67,235,11,66,111,15,32,254,97,66,12,160,154,67, + 227,52,80,33,15,87,249,15,93,45,31,75,111,12,93,45,11,77,99,9,160,184,81,31,12,32,15,98,135,30,104,175,7,77,249,36,69,73,15,78,5,12,32,254,66,151,19,34,128,128,4,87,32,12,149,35,133, + 21,96,151,31,32,19,72,35,5,98,173,15,143,15,32,21,143,99,158,129,33,0,0,65,35,52,65,11,15,147,15,98,75,11,33,1,0,143,151,132,15,32,254,99,200,37,132,43,130,4,39,0,10,0,128,1,128,3, + 0,104,151,14,97,187,20,69,131,15,67,195,11,87,227,7,33,128,128,132,128,33,254,0,68,131,9,65,46,26,42,0,0,0,7,0,0,255,128,3,128,0,88,223,15,33,0,21,89,61,22,66,209,12,65,2,12,37,0,2, + 1,0,3,128,101,83,8,36,0,1,53,51,29,130,3,34,21,1,0,66,53,8,32,0,68,215,6,100,55,25,107,111,9,66,193,11,72,167,8,73,143,31,139,31,33,1,0,131,158,32,254,132,5,33,253,128,65,16,9,133, + 17,89,130,25,141,212,33,0,0,93,39,8,90,131,25,93,39,14,66,217,6,106,179,8,159,181,71,125,15,139,47,138,141,87,11,14,76,23,14,65,231,26,140,209,66,122,8,81,179,5,101,195,26,32,47,74, + 75,13,69,159,11,83,235,11,67,21,16,136,167,131,106,130,165,130,15,32,128,101,90,24,134,142,32,0,65,103,51,108,23,11,101,231,15,75,173,23,74,237,23,66,15,6,66,46,17,66,58,17,65,105, + 49,66,247,55,71,179,12,70,139,15,86,229,7,84,167,15,32,1,95,72,12,89,49,6,33,128,128,65,136,38,66,30,9,32,0,100,239,7,66,247,29,70,105,20,65,141,19,69,81,15,130,144,32,128,83,41,5, + 32,255,131,177,68,185,5,133,126,65,97,37,32,0,130,0,33,21,0,130,55,66,195,28,67,155,13,34,79,0,83,66,213,13,73,241,19,66,59,19,65,125,11,135,201,66,249,16,32,128,66,44,11,66,56,17, + 68,143,8,68,124,38,67,183,12,96,211,9,65,143,29,112,171,5,32,0,68,131,63,34,33,53,51,71,121,11,32,254,98,251,16,32,253,74,231,10,65,175,37,133,206,37,0,0,8,1,0,0,107,123,11,113,115, + 9,33,0,1,130,117,131,3,73,103,7,66,51,18,66,44,5,133,75,70,88,5,32,254,65,39,12,68,80,9,34,12,0,128,107,179,28,68,223,6,155,111,86,147,15,32,2,131,82,141,110,33,254,0,130,15,32,4,103, + 184,15,141,35,87,176,5,83,11,5,71,235,23,114,107,11,65,189,16,70,33,15,86,153,31,135,126,86,145,30,65,183,41,32,0,130,0,32,10,65,183,24,34,35,0,39,67,85,9,65,179,15,143,15,33,1,0,65, + 28,17,157,136,130,123,32,20,130,3,32,0,97,135,24,115,167,19,80,71,12,32,51,110,163,14,78,35,19,131,19,155,23,77,229,8,78,9,17,151,17,67,231,46,94,135,8,73,31,31,93,215,56,82,171,25, + 72,77,8,162,179,169,167,99,131,11,69,85,19,66,215,15,76,129,13,68,115,22,72,79,35,67,113,5,34,0,0,19,70,31,46,65,89,52,73,223,15,85,199,33,95,33,8,132,203,73,29,32,67,48,16,177,215, + 101,13,15,65,141,43,69,141,15,75,89,5,70,0,11,70,235,21,178,215,36,10,0,128,0,0,71,207,24,33,0,19,100,67,6,80,215,11,66,67,7,80,43,12,71,106,7,80,192,5,65,63,5,66,217,26,33,0,13,156, + 119,68,95,5,72,233,12,134,129,85,81,11,76,165,20,65,43,8,73,136,8,75,10,31,38,128,128,0,0,0,13,1,130,4,32,3,106,235,29,114,179,12,66,131,23,32,7,77,133,6,67,89,12,131,139,116,60,9, + 89,15,37,32,0,74,15,7,103,11,22,65,35,5,33,55,0,93,81,28,67,239,23,78,85,5,107,93,14,66,84,17,65,193,26,74,183,10,66,67,34,143,135,79,91,15,32,7,117,111,8,75,56,9,84,212,9,154,134, + 32,0,130,0,32,18,130,3,70,171,41,83,7,16,70,131,19,84,191,15,84,175,19,84,167,30,84,158,12,154,193,68,107,15,33,0,0,65,79,42,65,71,7,73,55,7,118,191,16,83,180,9,32,255,76,166,9,154, + 141,32,0,130,0,69,195,52,65,225,15,151,15,75,215,31,80,56,10,68,240,17,100,32,9,70,147,39,65,93,12,71,71,41,92,85,15,84,135,23,78,35,15,110,27,10,84,125,8,107,115,29,136,160,38,0,0, + 14,0,128,255,0,82,155,24,67,239,8,119,255,11,69,131,11,77,29,6,112,31,8,134,27,105,203,8,32,2,75,51,11,75,195,12,74,13,29,136,161,37,128,0,0,0,11,1,130,163,82,115,8,125,191,17,69,35, + 12,74,137,15,143,15,32,1,65,157,12,136,12,161,142,65,43,40,65,199,6,65,19,24,102,185,11,76,123,11,99,6,12,135,12,32,254,130,8,161,155,101,23,9,39,8,0,0,1,128,3,128,2,78,63,17,72,245, + 12,67,41,11,90,167,9,32,128,97,49,9,32,128,109,51,14,132,97,81,191,8,130,97,125,99,12,121,35,9,127,75,15,71,79,12,81,151,23,87,97,7,70,223,15,80,245,16,105,97,15,32,254,113,17,6,32, + 128,130,8,105,105,8,76,122,18,65,243,21,74,63,7,38,4,1,0,255,0,2,0,119,247,28,133,65,32,255,141,91,35,0,0,0,16,67,63,36,34,59,0,63,77,59,9,119,147,11,143,241,66,173,15,66,31,11,67, + 75,8,81,74,16,32,128,131,255,87,181,42,127,43,5,34,255,128,2,120,235,11,37,19,0,23,0,0,37,109,191,14,118,219,7,127,43,14,65,79,14,35,0,0,0,3,73,91,5,130,5,38,3,0,7,0,11,0,0,70,205, + 11,88,221,12,32,0,73,135,7,87,15,22,73,135,10,79,153,15,97,71,19,65,49,11,32,1,131,104,121,235,11,80,65,11,142,179,144,14,81,123,46,32,1,88,217,5,112,5,8,65,201,15,83,29,15,122,147, + 11,135,179,142,175,143,185,67,247,39,66,199,7,35,5,0,128,3,69,203,15,123,163,12,67,127,7,130,119,71,153,10,141,102,70,175,8,32,128,121,235,30,136,89,100,191,11,116,195,11,111,235,15, + 72,39,7,32,2,97,43,5,132,5,94,67,8,131,8,125,253,10,32,3,65,158,16,146,16,130,170,40,0,21,0,128,0,0,3,128,5,88,219,15,24,64,159,32,135,141,65,167,15,68,163,10,97,73,49,32,255,82,58, + 7,93,80,8,97,81,16,24,67,87,52,34,0,0,5,130,231,33,128,2,80,51,13,65,129,8,113,61,6,132,175,65,219,5,130,136,77,152,17,32,0,95,131,61,70,215,6,33,21,51,90,53,10,78,97,23,105,77,31, + 65,117,7,139,75,24,68,195,9,24,64,22,9,33,0,128,130,11,33,128,128,66,25,5,121,38,5,134,5,134,45,66,40,36,66,59,18,34,128,0,0,66,59,81,135,245,123,103,19,120,159,19,77,175,12,33,255, + 0,87,29,10,94,70,21,66,59,54,39,3,1,128,3,0,2,128,4,24,65,7,15,66,47,7,72,98,12,37,0,0,0,3,1,0,24,65,55,21,131,195,32,1,67,178,6,33,4,0,77,141,8,32,6,131,47,74,67,16,24,69,3,20,24, + 65,251,7,133,234,130,229,94,108,17,35,0,0,6,0,141,175,86,59,5,162,79,85,166,8,70,112,13,32,13,24,64,67,26,24,71,255,7,123,211,12,80,121,11,69,215,15,66,217,11,69,71,10,131,113,132, + 126,119,90,9,66,117,19,132,19,32,0,130,0,24,64,47,59,33,7,0,73,227,5,68,243,15,85,13,12,76,37,22,74,254,15,130,138,33,0,4,65,111,6,137,79,65,107,16,32,1,77,200,6,34,128,128,3,75,154, + 12,37,0,16,0,0,2,0,104,115,36,140,157,68,67,19,68,51,15,106,243,15,134,120,70,37,10,68,27,10,140,152,65,121,24,32,128,94,155,7,67,11,8,24,74,11,25,65,3,12,83,89,18,82,21,37,67,200, + 5,130,144,24,64,172,12,33,4,0,134,162,74,80,14,145,184,32,0,130,0,69,251,20,32,19,81,243,5,82,143,8,33,5,53,89,203,5,133,112,79,109,15,33,0,21,130,71,80,175,41,36,75,0,79,0,83,121, + 117,9,87,89,27,66,103,11,70,13,15,75,191,11,135,67,87,97,20,109,203,5,69,246,8,108,171,5,78,195,38,65,51,13,107,203,11,77,3,17,24,75,239,17,65,229,28,79,129,39,130,175,32,128,123,253, + 7,132,142,24,65,51,15,65,239,41,36,128,128,0,0,13,65,171,5,66,163,28,136,183,118,137,11,80,255,15,67,65,7,74,111,8,32,0,130,157,32,253,24,76,35,10,103,212,5,81,175,9,69,141,7,66,150, + 29,131,158,24,75,199,28,124,185,7,76,205,15,68,124,14,32,3,123,139,16,130,16,33,128,128,108,199,6,33,0,3,65,191,35,107,11,6,73,197,11,24,70,121,15,83,247,15,24,70,173,23,69,205,14, + 32,253,131,140,32,254,136,4,94,198,9,32,3,78,4,13,66,127,13,143,13,32,0,130,0,33,16,0,24,69,59,39,109,147,12,76,253,19,24,69,207,15,69,229,15,130,195,71,90,10,139,10,130,152,73,43, + 40,91,139,10,65,131,37,35,75,0,79,0,84,227,12,143,151,68,25,15,80,9,23,95,169,11,34,128,2,128,112,186,5,130,6,83,161,19,76,50,6,130,37,65,145,44,110,83,5,32,16,67,99,6,71,67,15,76, + 55,17,140,215,67,97,23,76,69,15,77,237,11,104,211,23,77,238,11,65,154,43,33,0,10,83,15,28,83,13,20,67,145,19,67,141,14,97,149,21,68,9,15,86,251,5,66,207,5,66,27,37,82,1,23,127,71,12, + 94,235,10,110,175,24,98,243,15,132,154,132,4,24,66,69,10,32,4,67,156,43,130,198,35,2,1,0,4,75,27,9,69,85,9,95,240,7,32,128,130,35,32,28,66,43,40,24,82,63,23,83,123,12,72,231,15,127, + 59,23,116,23,19,117,71,7,24,77,99,15,67,111,15,71,101,8,36,2,128,128,252,128,127,60,11,32,1,132,16,130,18,141,24,67,107,9,32,3,68,194,15,175,15,38,0,11,0,128,1,128,2,80,63,25,32,0, + 24,65,73,11,69,185,15,83,243,16,32,0,24,81,165,8,130,86,77,35,6,155,163,88,203,5,24,66,195,30,70,19,19,24,80,133,15,32,1,75,211,8,32,254,108,133,8,79,87,20,65,32,9,41,0,0,7,0,128,0, + 0,2,128,2,68,87,15,66,1,16,92,201,16,24,76,24,17,133,17,34,128,0,30,66,127,64,34,115,0,119,73,205,9,66,43,11,109,143,15,24,79,203,11,90,143,15,131,15,155,31,65,185,15,86,87,11,35,128, + 128,253,0,69,7,6,130,213,33,1,0,119,178,15,142,17,66,141,74,83,28,6,36,7,0,0,4,128,82,39,18,76,149,12,67,69,21,32,128,79,118,15,32,0,130,0,32,8,131,206,32,2,79,83,9,100,223,14,102, + 113,23,115,115,7,24,65,231,12,130,162,32,4,68,182,19,130,102,93,143,8,69,107,29,24,77,255,12,143,197,72,51,7,76,195,15,132,139,85,49,15,130,152,131,18,71,81,23,70,14,11,36,0,10,0,128, + 2,69,59,9,89,151,15,66,241,11,76,165,12,71,43,15,75,49,13,65,12,23,132,37,32,0,179,115,130,231,95,181,16,132,77,32,254,67,224,8,65,126,20,79,171,8,32,2,89,81,5,75,143,6,80,41,8,34, + 2,0,128,24,81,72,9,32,0,130,0,35,17,0,0,255,77,99,39,95,65,36,67,109,15,24,69,93,11,77,239,5,95,77,23,35,128,1,0,128,24,86,7,8,132,167,32,2,69,198,41,130,202,33,0,26,120,75,44,24,89, + 51,15,71,243,12,70,239,11,24,84,3,11,66,7,11,71,255,10,32,21,69,155,35,88,151,12,32,128,74,38,10,65,210,8,74,251,5,65,226,5,75,201,13,32,3,65,9,41,146,41,40,0,0,0,9,1,0,1,0,2,91,99, + 19,32,35,106,119,13,70,219,15,83,239,12,137,154,32,2,67,252,19,36,128,0,0,4,1,130,196,32,2,130,8,91,107,8,32,0,135,81,24,73,211,8,132,161,73,164,13,36,0,8,0,128,2,105,123,26,139,67, + 76,99,15,34,1,0,128,135,76,83,156,20,92,104,8,67,251,30,24,86,47,27,123,207,12,24,86,7,15,71,227,8,32,4,65,20,20,131,127,32,0,130,123,32,0,71,223,26,32,19,90,195,22,71,223,15,84,200, + 6,32,128,133,241,24,84,149,9,67,41,25,36,0,0,0,22,0,88,111,49,32,87,66,21,5,77,3,27,123,75,7,71,143,19,135,183,71,183,19,130,171,74,252,5,131,5,89,87,17,32,1,132,18,130,232,68,11,10, + 33,1,128,70,208,16,66,230,18,147,18,130,254,223,255,75,27,23,65,59,15,135,39,155,255,34,128,128,254,104,92,8,33,0,128,65,32,11,65,1,58,33,26,0,130,0,72,71,18,78,55,17,76,11,19,86,101, + 12,75,223,11,89,15,11,24,76,87,15,75,235,15,131,15,72,95,7,85,71,11,72,115,11,73,64,6,34,1,128,128,66,215,9,34,128,254,128,134,14,33,128,255,67,102,5,32,0,130,16,70,38,11,66,26,57, + 88,11,8,24,76,215,34,78,139,7,95,245,7,32,7,24,73,75,23,32,128,131,167,130,170,101,158,9,82,49,22,118,139,6,32,18,67,155,44,116,187,9,108,55,14,80,155,23,66,131,15,93,77,10,131,168, + 32,128,73,211,12,24,75,187,22,32,4,96,71,20,67,108,19,132,19,120,207,8,32,5,76,79,15,66,111,21,66,95,8,32,3,190,211,111,3,8,211,212,32,20,65,167,44,34,75,0,79,97,59,13,32,33,112,63, + 10,65,147,19,69,39,19,143,39,24,66,71,9,130,224,65,185,43,94,176,12,65,183,24,71,38,8,24,72,167,7,65,191,38,136,235,24,96,167,12,65,203,62,115,131,13,65,208,42,175,235,67,127,6,32, + 4,76,171,29,114,187,5,32,71,65,211,5,65,203,68,72,51,8,164,219,32,0,172,214,71,239,58,78,3,27,66,143,15,77,19,15,147,31,35,33,53,51,21,66,183,10,173,245,66,170,30,150,30,34,0,0,23, + 80,123,54,76,1,16,73,125,15,82,245,11,167,253,24,76,85,12,70,184,5,32,254,131,185,37,254,0,128,1,0,128,133,16,117,158,18,92,27,38,65,3,17,130,251,35,17,0,128,254,24,69,83,39,140,243, + 121,73,19,109,167,7,81,41,15,24,95,175,12,102,227,15,121,96,11,24,95,189,7,32,3,145,171,154,17,24,77,47,9,33,0,5,70,71,37,68,135,7,32,29,117,171,11,69,87,15,24,79,97,19,24,79,149,23, + 131,59,32,1,75,235,5,72,115,11,72,143,7,132,188,71,27,46,131,51,32,0,69,95,6,175,215,32,21,131,167,81,15,19,151,191,151,23,131,215,71,43,5,32,254,24,79,164,24,74,109,8,77,166,13,65, + 176,26,88,162,5,98,159,6,171,219,120,247,6,79,29,8,99,169,10,103,59,19,65,209,35,131,35,91,25,19,112,94,15,83,36,8,173,229,33,20,0,88,75,43,71,31,12,65,191,71,33,1,0,130,203,32,254, + 131,4,68,66,7,67,130,6,104,61,13,173,215,38,13,1,0,0,0,2,128,67,111,28,74,129,16,104,35,19,79,161,16,87,14,7,138,143,132,10,67,62,36,114,115,5,162,151,67,33,16,108,181,15,143,151,67, + 5,5,24,100,242,15,170,153,34,0,0,14,65,51,34,32,55,79,75,9,32,51,74,7,10,65,57,38,132,142,32,254,72,0,14,139,163,32,128,80,254,8,67,158,21,65,63,7,32,4,72,227,27,95,155,12,67,119,19, + 124,91,24,149,154,72,177,34,97,223,8,155,151,24,108,227,15,88,147,16,72,117,19,68,35,11,92,253,15,70,199,15,24,87,209,17,32,2,87,233,7,32,1,24,88,195,10,119,24,8,32,3,81,227,24,65, + 125,21,35,128,128,0,25,76,59,48,24,90,187,9,97,235,12,66,61,11,91,105,19,24,79,141,11,24,79,117,15,24,79,129,27,90,53,13,130,13,32,253,131,228,24,79,133,40,69,70,8,66,137,31,65,33, + 19,96,107,8,68,119,29,66,7,5,68,125,16,65,253,19,65,241,27,24,90,179,13,24,79,143,18,33,128,128,130,246,32,254,130,168,68,154,36,77,51,9,97,47,5,167,195,32,21,131,183,78,239,27,155, + 195,78,231,14,201,196,77,11,6,32,5,73,111,37,97,247,12,77,19,31,155,207,78,215,19,162,212,69,17,14,66,91,19,80,143,57,78,203,39,159,215,32,128,93,134,8,24,80,109,24,66,113,15,169,215, + 66,115,6,32,4,69,63,33,32,0,101,113,7,86,227,35,143,211,36,49,53,51,21,1,77,185,14,65,159,28,69,251,34,67,56,8,33,9,0,24,107,175,25,90,111,12,110,251,11,119,189,24,119,187,34,87,15, + 9,32,4,66,231,37,90,39,7,66,239,8,84,219,15,69,105,23,24,85,27,27,87,31,11,33,1,128,76,94,6,32,1,85,241,7,33,128,128,106,48,10,33,128,128,69,136,11,133,13,24,79,116,49,84,236,8,24, + 91,87,9,32,5,165,255,69,115,12,66,27,15,159,15,24,72,247,12,74,178,5,24,80,64,15,33,0,128,143,17,77,89,51,130,214,24,81,43,7,170,215,74,49,8,159,199,143,31,139,215,69,143,5,32,254, + 24,81,50,35,181,217,84,123,70,143,195,159,15,65,187,16,66,123,7,65,175,15,65,193,29,68,207,39,79,27,5,70,131,6,32,4,68,211,33,33,67,0,83,143,14,159,207,143,31,140,223,33,0,128,24,80, + 82,14,24,93,16,23,32,253,65,195,5,68,227,40,133,214,107,31,7,32,5,67,115,27,87,9,8,107,31,43,66,125,6,32,0,103,177,23,131,127,72,203,36,32,0,110,103,8,155,163,73,135,6,32,19,24,112, + 99,10,65,71,11,73,143,19,143,31,126,195,5,24,85,21,9,24,76,47,14,32,254,24,93,77,36,68,207,11,39,25,0,0,255,128,3,128,4,66,51,37,95,247,13,82,255,24,76,39,19,147,221,66,85,27,24,118, + 7,8,24,74,249,12,76,74,8,91,234,8,67,80,17,131,222,33,253,0,121,30,44,73,0,16,69,15,6,32,0,65,23,38,69,231,12,65,179,6,98,131,16,86,31,27,24,108,157,14,80,160,8,24,65,46,17,33,4,0, + 96,2,18,144,191,65,226,8,68,19,5,171,199,80,9,15,180,199,67,89,5,32,255,24,79,173,28,174,201,24,79,179,50,32,1,24,122,5,10,82,61,10,180,209,83,19,8,32,128,24,80,129,27,111,248,43,131, + 71,24,115,103,8,67,127,41,78,213,24,100,247,19,66,115,39,75,107,5,32,254,165,219,78,170,40,24,112,163,49,32,1,97,203,6,65,173,64,32,0,83,54,7,133,217,88,37,12,32,254,131,28,33,128, + 3,67,71,44,84,183,6,32,5,69,223,33,96,7,7,123,137,16,192,211,24,112,14,9,32,255,67,88,29,68,14,10,84,197,38,33,0,22,116,47,50,32,87,106,99,9,116,49,15,89,225,15,97,231,23,70,41,19, + 82,85,8,93,167,6,32,253,132,236,108,190,7,89,251,5,116,49,58,33,128,128,131,234,32,15,24,74,67,38,70,227,24,24,83,45,23,89,219,12,70,187,12,89,216,19,32,2,69,185,24,141,24,70,143,66, + 24,82,119,56,78,24,10,32,253,133,149,132,6,24,106,233,7,69,198,48,178,203,81,243,12,68,211,15,106,255,23,66,91,15,69,193,7,100,39,10,24,83,72,16,176,204,33,19,0,88,207,45,68,21,12, + 68,17,10,65,157,53,68,17,6,32,254,92,67,10,65,161,25,69,182,43,24,118,91,47,69,183,18,181,209,111,253,12,89,159,8,66,112,12,69,184,45,35,0,0,0,9,24,80,227,26,73,185,16,118,195,15,131, + 15,33,1,0,65,59,15,66,39,27,160,111,66,205,12,148,111,143,110,33,128,128,156,112,24,81,199,8,75,199,23,66,117,20,155,121,32,254,68,126,12,72,213,29,134,239,149,123,89,27,16,148,117, + 65,245,8,24,71,159,14,141,134,134,28,73,51,55,109,77,15,105,131,11,68,67,11,76,169,27,107,209,12,102,174,8,32,128,72,100,18,116,163,56,79,203,11,75,183,44,85,119,19,71,119,23,151,227, + 32,1,93,27,8,65,122,5,77,102,8,110,120,20,66,23,8,66,175,17,66,63,12,133,12,79,35,8,74,235,33,67,149,16,69,243,15,78,57,15,69,235,16,67,177,7,151,192,130,23,67,84,29,141,192,174,187, + 77,67,15,69,11,12,159,187,77,59,10,199,189,24,70,235,50,96,83,19,66,53,23,105,65,19,77,47,12,163,199,66,67,37,78,207,50,67,23,23,174,205,67,228,6,71,107,13,67,22,14,66,85,11,83,187, + 38,124,47,49,95,7,19,66,83,23,67,23,19,24,96,78,17,80,101,16,71,98,40,33,0,7,88,131,22,24,89,245,12,84,45,12,102,213,5,123,12,9,32,2,126,21,14,43,255,0,128,128,0,0,20,0,128,255,128, + 3,126,19,39,32,75,106,51,7,113,129,15,24,110,135,19,126,47,15,115,117,11,69,47,11,32,2,109,76,9,102,109,9,32,128,75,2,10,130,21,32,254,69,47,6,32,3,94,217,47,32,0,65,247,10,69,15,46, + 65,235,31,65,243,15,101,139,10,66,174,14,65,247,16,72,102,28,69,17,14,84,243,9,165,191,88,47,48,66,53,12,32,128,71,108,6,203,193,32,17,75,187,42,73,65,16,65,133,52,114,123,9,167,199, + 69,21,37,86,127,44,75,171,11,180,197,78,213,12,148,200,81,97,46,24,95,243,9,32,4,66,75,33,113,103,9,87,243,36,143,225,24,84,27,31,90,145,8,148,216,67,49,5,24,84,34,14,75,155,27,67, + 52,13,140,13,36,0,20,0,128,255,24,135,99,46,88,59,43,155,249,80,165,7,136,144,71,161,23,32,253,132,33,32,254,88,87,44,136,84,35,128,0,0,21,81,103,5,94,47,44,76,51,12,143,197,151,15, + 65,215,31,24,64,77,13,65,220,20,65,214,14,71,4,40,65,213,13,32,0,130,0,35,21,1,2,0,135,0,34,36,0,72,134,10,36,1,0,26,0,130,134,11,36,2,0,14,0,108,134,11,32,3,138,23,32,4,138,11,34, + 5,0,20,134,33,34,0,0,6,132,23,32,1,134,15,32,18,130,25,133,11,37,1,0,13,0,49,0,133,11,36,2,0,7,0,38,134,11,36,3,0,17,0,45,134,11,32,4,138,35,36,5,0,10,0,62,134,23,32,6,132,23,36,3, + 0,1,4,9,130,87,131,167,133,11,133,167,133,11,133,167,133,11,37,3,0,34,0,122,0,133,11,133,167,133,11,133,167,133,11,133,167,34,50,0,48,130,1,34,52,0,47,134,5,8,49,49,0,53,98,121,32, + 84,114,105,115,116,97,110,32,71,114,105,109,109,101,114,82,101,103,117,108,97,114,84,84,88,32,80,114,111,103,103,121,67,108,101,97,110,84,84,50,48,48,52,47,130,2,53,49,53,0,98,0,121, + 0,32,0,84,0,114,0,105,0,115,0,116,0,97,0,110,130,15,32,71,132,15,36,109,0,109,0,101,130,9,32,82,130,5,36,103,0,117,0,108,130,29,32,114,130,43,34,84,0,88,130,35,32,80,130,25,34,111, + 0,103,130,1,34,121,0,67,130,27,32,101,132,59,32,84,130,31,33,0,0,65,155,9,34,20,0,0,65,11,6,130,8,135,2,33,1,1,130,9,8,120,1,1,2,1,3,1,4,1,5,1,6,1,7,1,8,1,9,1,10,1,11,1,12,1,13,1,14, + 1,15,1,16,1,17,1,18,1,19,1,20,1,21,1,22,1,23,1,24,1,25,1,26,1,27,1,28,1,29,1,30,1,31,1,32,0,3,0,4,0,5,0,6,0,7,0,8,0,9,0,10,0,11,0,12,0,13,0,14,0,15,0,16,0,17,0,18,0,19,0,20,0,21,0, + 22,0,23,0,24,0,25,0,26,0,27,0,28,0,29,0,30,0,31,130,187,8,66,33,0,34,0,35,0,36,0,37,0,38,0,39,0,40,0,41,0,42,0,43,0,44,0,45,0,46,0,47,0,48,0,49,0,50,0,51,0,52,0,53,0,54,0,55,0,56,0, + 57,0,58,0,59,0,60,0,61,0,62,0,63,0,64,0,65,0,66,130,243,9,75,68,0,69,0,70,0,71,0,72,0,73,0,74,0,75,0,76,0,77,0,78,0,79,0,80,0,81,0,82,0,83,0,84,0,85,0,86,0,87,0,88,0,89,0,90,0,91,0, + 92,0,93,0,94,0,95,0,96,0,97,1,33,1,34,1,35,1,36,1,37,1,38,1,39,1,40,1,41,1,42,1,43,1,44,1,45,1,46,1,47,1,48,1,49,1,50,1,51,1,52,1,53,1,54,1,55,1,56,1,57,1,58,1,59,1,60,1,61,1,62,1, + 63,1,64,1,65,0,172,0,163,0,132,0,133,0,189,0,150,0,232,0,134,0,142,0,139,0,157,0,169,0,164,0,239,0,138,0,218,0,131,0,147,0,242,0,243,0,141,0,151,0,136,0,195,0,222,0,241,0,158,0,170, + 0,245,0,244,0,246,0,162,0,173,0,201,0,199,0,174,0,98,0,99,0,144,0,100,0,203,0,101,0,200,0,202,0,207,0,204,0,205,0,206,0,233,0,102,0,211,0,208,0,209,0,175,0,103,0,240,0,145,0,214,0, + 212,0,213,0,104,0,235,0,237,0,137,0,106,0,105,0,107,0,109,0,108,0,110,0,160,0,111,0,113,0,112,0,114,0,115,0,117,0,116,0,118,0,119,0,234,0,120,0,122,0,121,0,123,0,125,0,124,0,184,0, + 161,0,127,0,126,0,128,0,129,0,236,0,238,0,186,14,117,110,105,99,111,100,101,35,48,120,48,48,48,49,141,14,32,50,141,14,32,51,141,14,32,52,141,14,32,53,141,14,32,54,141,14,32,55,141, + 14,32,56,141,14,32,57,141,14,32,97,141,14,32,98,141,14,32,99,141,14,32,100,141,14,32,101,141,14,32,102,140,14,33,49,48,141,14,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49, + 141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,45,49,102,6,100,101,108,101,116,101,4,69,117,114, + 111,140,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236, + 32,56,141,236,32,56,141,236,32,56,65,220,13,32,57,65,220,13,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141, + 239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,35,57,102,0,0,5,250,72,249,98,247, +}; + +static const char* GetDefaultCompressedFontDataTTF(int* out_size) +{ + *out_size = proggy_clean_ttf_compressed_size; + return (const char*)proggy_clean_ttf_compressed_data; } +#endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT #endif // #ifndef IMGUI_DISABLE diff --git a/ext/imgui/imgui_impl_platform.cpp b/ext/imgui/imgui_impl_platform.cpp index f69c2c02abd2..e0afb444d281 100644 --- a/ext/imgui/imgui_impl_platform.cpp +++ b/ext/imgui/imgui_impl_platform.cpp @@ -79,6 +79,7 @@ void ImGui_ImplPlatform_Init(const Path &configPath) { static char path[1024]; truncate_cpy(path, configPath.ToString()); ImGui::GetIO().IniFilename = path; + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable; } void ImGui_ImplPlatform_AxisEvent(const AxisInput &axis) { diff --git a/ext/imgui/imgui_internal.h b/ext/imgui/imgui_internal.h index 244cda1a28ae..86aed947df4d 100644 --- a/ext/imgui/imgui_internal.h +++ b/ext/imgui/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.90.9 WIP +// dear imgui, v1.91.6 // (internal structures/api) // You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility. @@ -22,11 +22,13 @@ Index of this file: // [SECTION] Navigation support // [SECTION] Typing-select support // [SECTION] Columns support +// [SECTION] Box-select support // [SECTION] Multi-select support // [SECTION] Docking support // [SECTION] Viewport support // [SECTION] Settings support // [SECTION] Localization support +// [SECTION] Error handling, State recovery support // [SECTION] Metrics, Debug tools // [SECTION] Generic context hooks // [SECTION] ImGuiContext (main imgui context) @@ -59,6 +61,14 @@ Index of this file: #if (defined __SSE__ || defined __x86_64__ || defined _M_X64 || (defined(_M_IX86_FP) && (_M_IX86_FP >= 1))) && !defined(IMGUI_DISABLE_SSE) #define IMGUI_ENABLE_SSE #include +#if (defined __AVX__ || defined __SSE4_2__) +#define IMGUI_ENABLE_SSE4_2 +#include +#endif +#endif +// Emscripten has partial SSE 4.2 support where _mm_crc32_u32 is not available. See https://emscripten.org/docs/porting/simd.html#id11 and #8213 +#if defined(IMGUI_ENABLE_SSE4_2) && !defined(IMGUI_USE_LEGACY_CRC32_ADLER) && !defined(__EMSCRIPTEN__) +#define IMGUI_ENABLE_SSE4_2_CRC #endif // Visual Studio warnings @@ -80,19 +90,19 @@ Index of this file: #endif #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' #pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants ok, for ImFloor() -#pragma clang diagnostic ignored "-Wunused-function" // for stb_textedit.h -#pragma clang diagnostic ignored "-Wmissing-prototypes" // for stb_textedit.h -#pragma clang diagnostic ignored "-Wold-style-cast" -#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" -#pragma clang diagnostic ignored "-Wdouble-promotion" +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wmissing-noreturn" // warning: function 'xxx' could be declared with attribute 'noreturn' #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type #elif defined(__GNUC__) #pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind -#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated #endif // In 1.89.4, we moved the implementation of "courtesy maths operators" from imgui_internal.h in imgui.h @@ -123,19 +133,26 @@ struct ImBitVector; // Store 1-bit per value struct ImRect; // An axis-aligned rectangle (2 points) struct ImDrawDataBuilder; // Helper to build a ImDrawData instance struct ImDrawListSharedData; // Data shared between all ImDrawList instances +struct ImGuiBoxSelectState; // Box-selection state (currently used by multi-selection, could potentially be used by others) struct ImGuiColorMod; // Stacked color modifier, backup of modified data so we can restore it struct ImGuiContext; // Main Dear ImGui context struct ImGuiContextHook; // Hook for extensions like ImGuiTestEngine struct ImGuiDataVarInfo; // Variable information (e.g. to access style variables from an enum) struct ImGuiDataTypeInfo; // Type information associated to a ImGuiDataType enum +struct ImGuiDockContext; // Docking system context +struct ImGuiDockRequest; // Docking system dock/undock queued request +struct ImGuiDockNode; // Docking system node (hold a list of Windows OR two child dock nodes) +struct ImGuiDockNodeSettings; // Storage for a dock node in .ini file (we preserve those even if the associated dock node isn't active during the session) +struct ImGuiErrorRecoveryState; // Storage of stack sizes for error handling and recovery struct ImGuiGroupData; // Stacked storage data for BeginGroup()/EndGroup() struct ImGuiInputTextState; // Internal state of the currently focused/edited text input box struct ImGuiInputTextDeactivateData;// Short term storage to backup text of a deactivating InputText() while another is stealing active id struct ImGuiLastItemData; // Status storage for last submitted items struct ImGuiLocEntry; // A localization entry. struct ImGuiMenuColumns; // Simple column measurement, currently used for MenuItem() only -struct ImGuiNavItemData; // Result of a gamepad/keyboard directional navigation move query result -struct ImGuiNavTreeNodeData; // Temporary storage for last TreeNode() being a Left arrow landing candidate. +struct ImGuiMultiSelectState; // Multi-selection persistent state (for focused selection). +struct ImGuiMultiSelectTempData; // Multi-selection temporary state (while traversing). +struct ImGuiNavItemData; // Result of a keyboard/gamepad directional navigation move query result struct ImGuiMetricsConfig; // Storage for ShowMetricsWindow() and DebugNodeXXX() functions struct ImGuiNextWindowData; // Storage for SetNextWindow** functions struct ImGuiNextItemData; // Storage for SetNextItem** functions @@ -143,7 +160,6 @@ struct ImGuiOldColumnData; // Storage data for a single column for lega struct ImGuiOldColumns; // Storage data for a columns set for legacy Columns() api struct ImGuiPopupData; // Storage for current popup stack struct ImGuiSettingsHandler; // Storage for one type registered in the .ini file -struct ImGuiStackSizes; // Storage of stack sizes for debugging/asserting struct ImGuiStyleMod; // Stacked style modifier, backup of modified data so we can restore it struct ImGuiTabBar; // Storage for a tab bar struct ImGuiTabItem; // Storage for a tab item (within a tab bar) @@ -154,25 +170,28 @@ struct ImGuiTableInstanceData; // Storage for one instance of a same table struct ImGuiTableTempData; // Temporary storage for one table (one per table in the stack), shared between tables. struct ImGuiTableSettings; // Storage for a table .ini settings struct ImGuiTableColumnsSettings; // Storage for a column .ini settings +struct ImGuiTreeNodeStackData; // Temporary storage for TreeNode(). struct ImGuiTypingSelectState; // Storage for GetTypingSelectRequest() struct ImGuiTypingSelectRequest; // Storage for GetTypingSelectRequest() (aimed to be public) struct ImGuiWindow; // Storage for one window +struct ImGuiWindowDockStyle; // Storage for window-style data which needs to be stored for docking purpose struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame, in practice we currently keep it for each window) struct ImGuiWindowSettings; // Storage for a window .ini settings (we keep one of those even if the actual window wasn't instanced during this session) // Enumerations // Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists. enum ImGuiLocKey : int; // -> enum ImGuiLocKey // Enum: a localization entry for translation. +typedef int ImGuiDataAuthority; // -> enum ImGuiDataAuthority_ // Enum: for storing the source authority (dock node vs window) of a field typedef int ImGuiLayoutType; // -> enum ImGuiLayoutType_ // Enum: Horizontal or vertical // Flags typedef int ImGuiActivateFlags; // -> enum ImGuiActivateFlags_ // Flags: for navigation/focus function (will be for ActivateItem() later) typedef int ImGuiDebugLogFlags; // -> enum ImGuiDebugLogFlags_ // Flags: for ShowDebugLogWindow(), g.DebugLogFlags -typedef int ImGuiFocusRequestFlags; // -> enum ImGuiFocusRequestFlags_ // Flags: for FocusWindow(); -typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag(), g.LastItemData.InFlags +typedef int ImGuiFocusRequestFlags; // -> enum ImGuiFocusRequestFlags_ // Flags: for FocusWindow() typedef int ImGuiItemStatusFlags; // -> enum ImGuiItemStatusFlags_ // Flags: for g.LastItemData.StatusFlags typedef int ImGuiOldColumnFlags; // -> enum ImGuiOldColumnFlags_ // Flags: for BeginColumns() -typedef int ImGuiNavHighlightFlags; // -> enum ImGuiNavHighlightFlags_ // Flags: for RenderNavHighlight() +typedef int ImGuiLogFlags; // -> enum ImGuiLogFlags_ // Flags: for LogBegin() text capturing function +typedef int ImGuiNavRenderCursorFlags; // -> enum ImGuiNavRenderCursorFlags_//Flags: for RenderNavCursor() typedef int ImGuiNavMoveFlags; // -> enum ImGuiNavMoveFlags_ // Flags: for navigation requests typedef int ImGuiNextItemDataFlags; // -> enum ImGuiNextItemDataFlags_ // Flags: for SetNextItemXXX() functions typedef int ImGuiNextWindowDataFlags; // -> enum ImGuiNextWindowDataFlags_// Flags: for SetNextWindowXXX() functions @@ -183,8 +202,6 @@ typedef int ImGuiTooltipFlags; // -> enum ImGuiTooltipFlags_ // F typedef int ImGuiTypingSelectFlags; // -> enum ImGuiTypingSelectFlags_ // Flags: for GetTypingSelectRequest() typedef int ImGuiWindowRefreshFlags; // -> enum ImGuiWindowRefreshFlags_ // Flags: for SetNextWindowRefreshPolicy() -typedef void (*ImGuiErrorLogCallback)(void* user_data, const char* fmt, ...); - //----------------------------------------------------------------------------- // [SECTION] Context pointer // See implementation of this variable in imgui.cpp for comments and details. @@ -194,28 +211,13 @@ typedef void (*ImGuiErrorLogCallback)(void* user_data, const char* fmt, ...); extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #endif -//------------------------------------------------------------------------- -// [SECTION] STB libraries includes -//------------------------------------------------------------------------- - -namespace ImStb -{ - -#undef IMSTB_TEXTEDIT_STRING -#undef IMSTB_TEXTEDIT_CHARTYPE -#define IMSTB_TEXTEDIT_STRING ImGuiInputTextState -#define IMSTB_TEXTEDIT_CHARTYPE ImWchar -#define IMSTB_TEXTEDIT_GETWIDTH_NEWLINE (-1.0f) -#define IMSTB_TEXTEDIT_UNDOSTATECOUNT 99 -#define IMSTB_TEXTEDIT_UNDOCHARCOUNT 999 -#include "imstb_textedit.h" - -} // namespace ImStb - //----------------------------------------------------------------------------- // [SECTION] Macros //----------------------------------------------------------------------------- +// Internal Drag and Drop payload types. String starting with '_' are reserved for Dear ImGui. +#define IMGUI_PAYLOAD_TYPE_WINDOW "_IMWINDOW" // Payload == ImGuiWindow* + // Debug Printing Into TTY // (since IMGUI_VERSION_NUM >= 18729: IMGUI_DEBUG_LOG was reworked into IMGUI_DEBUG_PRINTF (and removed framecount from it). If you were using a #define IMGUI_DEBUG_LOG please rename) #ifndef IMGUI_DEBUG_PRINTF @@ -227,11 +229,7 @@ namespace ImStb #endif // Debug Logging for ShowDebugLogWindow(). This is designed for relatively rare events so please don't spam. -#ifndef IMGUI_DISABLE_DEBUG_TOOLS -#define IMGUI_DEBUG_LOG(...) ImGui::DebugLog(__VA_ARGS__) -#else -#define IMGUI_DEBUG_LOG(...) ((void)0) -#endif +#define IMGUI_DEBUG_LOG_ERROR(...) do { ImGuiContext& g2 = *GImGui; if (g2.DebugLogFlags & ImGuiDebugLogFlags_EventError) IMGUI_DEBUG_LOG(__VA_ARGS__); else g2.DebugLogSkippedErrors++; } while (0) #define IMGUI_DEBUG_LOG_ACTIVEID(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventActiveId) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_FOCUS(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventFocus) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_POPUP(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventPopup) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) @@ -239,7 +237,10 @@ namespace ImStb #define IMGUI_DEBUG_LOG_SELECTION(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_CLIPPER(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventClipper) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_IO(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) +#define IMGUI_DEBUG_LOG_FONT(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventFont) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_INPUTROUTING(...) do{if (g.DebugLogFlags & ImGuiDebugLogFlags_EventInputRouting)IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) +#define IMGUI_DEBUG_LOG_DOCKING(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventDocking) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) +#define IMGUI_DEBUG_LOG_VIEWPORT(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventViewport) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) // Static Asserts #define IM_STATIC_ASSERT(_COND) static_assert(_COND, "") @@ -253,12 +254,6 @@ namespace ImStb #define IM_ASSERT_PARANOID(_EXPR) #endif -// Error handling -// Down the line in some frameworks/languages we would like to have a way to redirect those to the programmer and recover from more faults. -#ifndef IM_ASSERT_USER_ERROR -#define IM_ASSERT_USER_ERROR(_EXP,_MSG) IM_ASSERT((_EXP) && _MSG) // Recoverable User Error -#endif - // Misc Macros #define IM_PI 3.14159265358979323846f #ifdef _WIN32 @@ -280,6 +275,15 @@ namespace ImStb #define IM_FLOOR IM_TRUNC #endif +// Hint for branch prediction +#if (defined(__cplusplus) && (__cplusplus >= 202002L)) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 202002L)) +#define IM_LIKELY [[likely]] +#define IM_UNLIKELY [[unlikely]] +#else +#define IM_LIKELY +#define IM_UNLIKELY +#endif + // Enforce cdecl calling convention for functions called by the standard library, in case compilation settings changed the default to e.g. __vectorcall #ifdef _MSC_VER #define IMGUI_CDECL __cdecl @@ -380,11 +384,12 @@ IMGUI_API const char* ImStristr(const char* haystack, const char* haystack_end IMGUI_API void ImStrTrimBlanks(char* str); // Remove leading and trailing blanks from a buffer. IMGUI_API const char* ImStrSkipBlank(const char* str); // Find first non-blank character. IMGUI_API int ImStrlenW(const ImWchar* str); // Computer string length (ImWchar string) -IMGUI_API const ImWchar*ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin); // Find beginning-of-line (ImWchar string) +IMGUI_API const char* ImStrbol(const char* buf_mid_line, const char* buf_begin); // Find beginning-of-line IM_MSVC_RUNTIME_CHECKS_OFF static inline char ImToUpper(char c) { return (c >= 'a' && c <= 'z') ? c &= ~32 : c; } static inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; } static inline bool ImCharIsBlankW(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; } +static inline bool ImCharIsXdigitA(char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } IM_MSVC_RUNTIME_CHECKS_RESTORE // Helpers: Formatting @@ -489,6 +494,7 @@ static inline int ImModPositive(int a, int b) static inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } static inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } +static inline float ImLinearRemapClamp(float s0, float s1, float d0, float d1, float x) { return ImSaturate((x - s0) / (s1 - s0)) * (d1 - d0) + d0; } static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } static inline bool ImIsFloatAboveGuaranteedIntegerPrecision(float f) { return f <= -16777216 || f >= 16777216; } static inline float ImExponentialMovingAverage(float avg, float sample, int n) { avg -= avg / n; avg += sample / n; return avg; } @@ -769,25 +775,26 @@ IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStorag #define IM_DRAWLIST_ARCFAST_SAMPLE_MAX IM_DRAWLIST_ARCFAST_TABLE_SIZE // Sample index _PathArcToFastEx() for 360 angle. // Data shared between all ImDrawList instances -// You may want to create your own instance of this if you want to use ImDrawList completely without ImGui. In that case, watch out for future changes to this structure. +// Conceptually this could have been called e.g. ImDrawListSharedContext +// Typically one ImGui context would create and maintain one of this. +// You may want to create your own instance of you try to ImDrawList completely without ImGui. In that case, watch out for future changes to this structure. struct IMGUI_API ImDrawListSharedData { ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas + const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas ImFont* Font; // Current/default font (optional, for simplified AddText overload) float FontSize; // Current/default font size (optional, for simplified AddText overload) + float FontScale; // Current/default font scale (== FontSize / Font->FontSize) float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() float CircleSegmentMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) + ImVector TempBuffer; // Temporary write buffer - // [Internal] Temp write buffer - ImVector TempBuffer; - - // [Internal] Lookup tables + // Lookup tables ImVec2 ArcFastVtx[IM_DRAWLIST_ARCFAST_TABLE_SIZE]; // Sample points on the quarter of the circle. float ArcFastRadiusCutoff; // Cutoff radius after which arc drawing will fallback to slower PathArcTo() ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius before we calculate it dynamically (to avoid calculation overhead) - const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas ImDrawListSharedData(); void SetCircleTessellationMaxError(float max_error); @@ -839,29 +846,29 @@ enum ImGuiDataTypePrivate_ // [SECTION] Widgets support: flags, enums, data structures //----------------------------------------------------------------------------- -// Flags used by upcoming items -// - input: PushItemFlag() manipulates g.CurrentItemFlags, ItemAdd() calls may add extra flags. -// - output: stored in g.LastItemData.InFlags -// Current window shared by all windows. -// This is going to be exposed in imgui.h when stabilized enough. -enum ImGuiItemFlags_ +// Extend ImGuiItemFlags +// - input: PushItemFlag() manipulates g.CurrentItemFlags, g.NextItemData.ItemFlags, ItemAdd() calls may add extra flags too. +// - output: stored in g.LastItemData.ItemFlags +enum ImGuiItemFlagsPrivate_ { // Controlled by user - ImGuiItemFlags_None = 0, - ImGuiItemFlags_NoTabStop = 1 << 0, // false // Disable keyboard tabbing. This is a "lighter" version of ImGuiItemFlags_NoNav. - ImGuiItemFlags_ButtonRepeat = 1 << 1, // false // Button() will return true multiple times based on io.KeyRepeatDelay and io.KeyRepeatRate settings. - ImGuiItemFlags_Disabled = 1 << 2, // false // Disable interactions but doesn't affect visuals. See BeginDisabled()/EndDisabled(). See github.com/ocornut/imgui/issues/211 - ImGuiItemFlags_NoNav = 1 << 3, // false // Disable any form of focusing (keyboard/gamepad directional navigation and SetKeyboardFocusHere() calls) - ImGuiItemFlags_NoNavDefaultFocus = 1 << 4, // false // Disable item being a candidate for default focus (e.g. used by title bar items) - ImGuiItemFlags_SelectableDontClosePopup = 1 << 5, // false // Disable MenuItem/Selectable() automatically closing their popup window - ImGuiItemFlags_MixedValue = 1 << 6, // false // [BETA] Represent a mixed/indeterminate value, generally multi-selection where values differ. Currently only supported by Checkbox() (later should support all sorts of widgets) - ImGuiItemFlags_ReadOnly = 1 << 7, // false // [ALPHA] Allow hovering interactions but underlying value is not changed. - ImGuiItemFlags_NoWindowHoverableCheck = 1 << 8, // false // Disable hoverable check in ItemHoverable() - ImGuiItemFlags_AllowOverlap = 1 << 9, // false // Allow being overlapped by another widget. Not-hovered to Hovered transition deferred by a frame. + ImGuiItemFlags_Disabled = 1 << 10, // false // Disable interactions (DOES NOT affect visuals. DO NOT mix direct use of this with BeginDisabled(). See BeginDisabled()/EndDisabled() for full disable feature, and github #211). + ImGuiItemFlags_ReadOnly = 1 << 11, // false // [ALPHA] Allow hovering interactions but underlying value is not changed. + ImGuiItemFlags_MixedValue = 1 << 12, // false // [BETA] Represent a mixed/indeterminate value, generally multi-selection where values differ. Currently only supported by Checkbox() (later should support all sorts of widgets) + ImGuiItemFlags_NoWindowHoverableCheck = 1 << 13, // false // Disable hoverable check in ItemHoverable() + ImGuiItemFlags_AllowOverlap = 1 << 14, // false // Allow being overlapped by another widget. Not-hovered to Hovered transition deferred by a frame. + ImGuiItemFlags_NoNavDisableMouseHover = 1 << 15, // false // Nav keyboard/gamepad mode doesn't disable hover highlight (behave as if NavHighlightItemUnderNav==false). + ImGuiItemFlags_NoMarkEdited = 1 << 16, // false // Skip calling MarkItemEdited() // Controlled by widget code - ImGuiItemFlags_Inputable = 1 << 10, // false // [WIP] Auto-activate input mode when tab focused. Currently only used and supported by a few items before it becomes a generic feature. - ImGuiItemFlags_HasSelectionUserData = 1 << 11, // false // Set by SetNextItemSelectionUserData() + ImGuiItemFlags_Inputable = 1 << 20, // false // [WIP] Auto-activate input mode when tab focused. Currently only used and supported by a few items before it becomes a generic feature. + ImGuiItemFlags_HasSelectionUserData = 1 << 21, // false // Set by SetNextItemSelectionUserData() + ImGuiItemFlags_IsMultiSelect = 1 << 22, // false // Set by SetNextItemSelectionUserData() + + ImGuiItemFlags_Default_ = ImGuiItemFlags_AutoClosePopups, // Please don't change, use PushItemFlag() instead. + + // Obsolete + //ImGuiItemFlags_SelectableDontClosePopup = !ImGuiItemFlags_AutoClosePopups, // Can't have a redirect as we inverted the behavior }; // Status flags for an already submitted item @@ -895,7 +902,7 @@ enum ImGuiItemStatusFlags_ enum ImGuiHoveredFlagsPrivate_ { ImGuiHoveredFlags_DelayMask_ = ImGuiHoveredFlags_DelayNone | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_NoSharedDelay, - ImGuiHoveredFlags_AllowedMaskForIsWindowHovered = ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_NoPopupHierarchy | ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_Stationary, + ImGuiHoveredFlags_AllowedMaskForIsWindowHovered = ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_NoPopupHierarchy | ImGuiHoveredFlags_DockHierarchy | ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_Stationary, ImGuiHoveredFlags_AllowedMaskForIsItemHovered = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped | ImGuiHoveredFlags_AllowWhenDisabled | ImGuiHoveredFlags_NoNavOverride | ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayMask_, }; @@ -904,9 +911,8 @@ enum ImGuiInputTextFlagsPrivate_ { // [Internal] ImGuiInputTextFlags_Multiline = 1 << 26, // For internal use by InputTextMultiline() - ImGuiInputTextFlags_NoMarkEdited = 1 << 27, // For internal use by functions using InputText() before reformatting data - ImGuiInputTextFlags_MergedItem = 1 << 28, // For internal use by TempInputText(), will skip calling ItemAdd(). Require bounding-box to strictly match. - ImGuiInputTextFlags_LocalizeDecimalPoint= 1 << 29, // For internal use by InputScalar() and TempInputScalar() + ImGuiInputTextFlags_MergedItem = 1 << 27, // For internal use by TempInputText(), will skip calling ItemAdd(). Require bounding-box to strictly match. + ImGuiInputTextFlags_LocalizeDecimalPoint= 1 << 28, // For internal use by InputScalar() and TempInputScalar() }; // Extend ImGuiButtonFlags_ @@ -918,15 +924,15 @@ enum ImGuiButtonFlagsPrivate_ ImGuiButtonFlags_PressedOnRelease = 1 << 7, // return true on release (default requires click+release) ImGuiButtonFlags_PressedOnDoubleClick = 1 << 8, // return true on double-click (default requires click+release) ImGuiButtonFlags_PressedOnDragDropHold = 1 << 9, // return true when held into while we are drag and dropping another item (used by e.g. tree nodes, collapsing headers) - ImGuiButtonFlags_Repeat = 1 << 10, // hold to repeat + //ImGuiButtonFlags_Repeat = 1 << 10, // hold to repeat ImGuiButtonFlags_FlattenChildren = 1 << 11, // allow interactions even if a child window is overlapping ImGuiButtonFlags_AllowOverlap = 1 << 12, // require previous frame HoveredId to either match id or be null before being usable. - ImGuiButtonFlags_DontClosePopups = 1 << 13, // disable automatically closing parent popup on press // [UNUSED] + //ImGuiButtonFlags_DontClosePopups = 1 << 13, // disable automatically closing parent popup on press //ImGuiButtonFlags_Disabled = 1 << 14, // disable interactions -> use BeginDisabled() or ImGuiItemFlags_Disabled ImGuiButtonFlags_AlignTextBaseLine = 1 << 15, // vertically align button to match text baseline - ButtonEx() only // FIXME: Should be removed and handled by SmallButton(), not possible currently because of DC.CursorPosPrevLine - ImGuiButtonFlags_NoKeyModifiers = 1 << 16, // disable mouse interaction if a key modifier is held + ImGuiButtonFlags_NoKeyModsAllowed = 1 << 16, // disable mouse interaction if a key modifier is held ImGuiButtonFlags_NoHoldingActiveId = 1 << 17, // don't set ActiveId while holding the mouse (ImGuiButtonFlags_PressedOnClick only) - ImGuiButtonFlags_NoNavFocus = 1 << 18, // don't override navigation focus when activated (FIXME: this is essentially used every time an item uses ImGuiItemFlags_NoNav, but because legacy specs don't requires LastItemData to be set ButtonBehavior(), we can't poll g.LastItemData.InFlags) + ImGuiButtonFlags_NoNavFocus = 1 << 18, // don't override navigation focus when activated (FIXME: this is essentially used every time an item uses ImGuiItemFlags_NoNav, but because legacy specs don't requires LastItemData to be set ButtonBehavior(), we can't poll g.LastItemData.ItemFlags) ImGuiButtonFlags_NoHoveredOnFocus = 1 << 19, // don't report as hovered when nav focus is on this item ImGuiButtonFlags_NoSetKeyOwner = 1 << 20, // don't set key/input owner on the initial click (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) ImGuiButtonFlags_NoTestKeyOwner = 1 << 21, // don't test key/input owner when polling the key (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) @@ -964,8 +970,9 @@ enum ImGuiSelectableFlagsPrivate_ // Extend ImGuiTreeNodeFlags_ enum ImGuiTreeNodeFlagsPrivate_ { - ImGuiTreeNodeFlags_ClipLabelForTrailingButton = 1 << 20, - ImGuiTreeNodeFlags_UpsideDownArrow = 1 << 21,// (FIXME-WIP) Turn Down arrow into an Up arrow, but reversed trees (#6517) + ImGuiTreeNodeFlags_ClipLabelForTrailingButton = 1 << 28,// FIXME-WIP: Hard-coded for CollapsingHeader() + ImGuiTreeNodeFlags_UpsideDownArrow = 1 << 29,// FIXME-WIP: Turn Down arrow into an Up arrow, for reversed trees (#6517) + ImGuiTreeNodeFlags_OpenOnMask_ = ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_OpenOnArrow, }; enum ImGuiSeparatorFlags_ @@ -1006,13 +1013,16 @@ enum ImGuiLayoutType_ ImGuiLayoutType_Vertical = 1 }; -enum ImGuiLogType +// Flags for LogBegin() text capturing function +enum ImGuiLogFlags_ { - ImGuiLogType_None = 0, - ImGuiLogType_TTY, - ImGuiLogType_File, - ImGuiLogType_Buffer, - ImGuiLogType_Clipboard, + ImGuiLogFlags_None = 0, + + ImGuiLogFlags_OutputTTY = 1 << 0, + ImGuiLogFlags_OutputFile = 1 << 1, + ImGuiLogFlags_OutputBuffer = 1 << 2, + ImGuiLogFlags_OutputClipboard = 1 << 3, + ImGuiLogFlags_OutputMask_ = ImGuiLogFlags_OutputTTY | ImGuiLogFlags_OutputFile | ImGuiLogFlags_OutputBuffer | ImGuiLogFlags_OutputClipboard, }; // X/Y enums are fixed to 0/1 so they may be used to index ImVec2 @@ -1104,20 +1114,31 @@ struct IMGUI_API ImGuiInputTextDeactivatedState ImGuiInputTextDeactivatedState() { memset(this, 0, sizeof(*this)); } void ClearFreeMemory() { ID = 0; TextA.clear(); } }; + +// Forward declare imstb_textedit.h structure + make its main configuration define accessible +#undef IMSTB_TEXTEDIT_STRING +#undef IMSTB_TEXTEDIT_CHARTYPE +#define IMSTB_TEXTEDIT_STRING ImGuiInputTextState +#define IMSTB_TEXTEDIT_CHARTYPE char +#define IMSTB_TEXTEDIT_GETWIDTH_NEWLINE (-1.0f) +#define IMSTB_TEXTEDIT_UNDOSTATECOUNT 99 +#define IMSTB_TEXTEDIT_UNDOCHARCOUNT 999 +namespace ImStb { struct STB_TexteditState; } +typedef ImStb::STB_TexteditState ImStbTexteditState; + // Internal state of the currently focused/edited text input box // For a given item ID, access with ImGui::GetInputTextState() struct IMGUI_API ImGuiInputTextState { ImGuiContext* Ctx; // parent UI context (needs to be set explicitly by parent). + ImStbTexteditState* Stb; // State for stb_textedit.h ImGuiID ID; // widget id owning the text state - int CurLenW, CurLenA; // we need to maintain our buffer length in both UTF-8 and wchar format. UTF-8 length is valid even if TextA is not. - ImVector TextW; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer. - ImVector TextA; // temporary UTF8 buffer for callbacks and other operations. this is not updated in every code-path! size=capacity. - ImVector InitialTextA; // value to revert to when pressing Escape = backup of end-user buffer at the time of focus (in UTF-8, unaltered) - bool TextAIsValid; // temporary UTF8 buffer is not initially valid before we make the widget active (until then we pull the data from user argument) - int BufCapacityA; // end-user buffer capacity - float ScrollX; // horizontal scrolling/offset - ImStb::STB_TexteditState Stb; // state for stb_textedit.h + int TextLen; // UTF-8 length of the string in TextA (in bytes) + ImVector TextA; // main UTF8 buffer. TextA.Size is a buffer size! Should always be >= buf_size passed by user (and of course >= CurLenA + 1). + ImVector TextToRevertTo; // value to revert to when pressing Escape = backup of end-user buffer at the time of focus (in UTF-8, unaltered) + ImVector CallbackTextBackup; // temporary storage for callback to support automatic reconcile of undo-stack + int BufCapacity; // end-user buffer capacity (include zero terminator) + ImVec2 Scroll; // horizontal offset (managed manually) + vertical scrolling (pulled from child window's own Scroll.y) float CursorAnim; // timer for cursor blink, reset on every user action so the cursor reappears immediately bool CursorFollow; // set when we want scrolling to follow the current cursor position (not always!) bool SelectedAllMouseLock; // after a double-click to select all, we ignore further mouse drags to update selection @@ -1127,32 +1148,31 @@ struct IMGUI_API ImGuiInputTextState int ReloadSelectionStart; // POSITIONS ARE IN IMWCHAR units *NOT* UTF-8 this is why this is not exposed yet. int ReloadSelectionEnd; - ImGuiInputTextState() { memset(this, 0, sizeof(*this)); } - void ClearText() { CurLenW = CurLenA = 0; TextW[0] = 0; TextA[0] = 0; CursorClamp(); } - void ClearFreeMemory() { TextW.clear(); TextA.clear(); InitialTextA.clear(); } - int GetUndoAvailCount() const { return Stb.undostate.undo_point; } - int GetRedoAvailCount() const { return IMSTB_TEXTEDIT_UNDOSTATECOUNT - Stb.undostate.redo_point; } + ImGuiInputTextState(); + ~ImGuiInputTextState(); + void ClearText() { TextLen = 0; TextA[0] = 0; CursorClamp(); } + void ClearFreeMemory() { TextA.clear(); TextToRevertTo.clear(); } void OnKeyPressed(int key); // Cannot be inline because we call in code in stb_textedit.h implementation + void OnCharPressed(unsigned int c); // Cursor & Selection - void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking - void CursorClamp() { Stb.cursor = ImMin(Stb.cursor, CurLenW); Stb.select_start = ImMin(Stb.select_start, CurLenW); Stb.select_end = ImMin(Stb.select_end, CurLenW); } - bool HasSelection() const { return Stb.select_start != Stb.select_end; } - void ClearSelection() { Stb.select_start = Stb.select_end = Stb.cursor; } - int GetCursorPos() const { return Stb.cursor; } - int GetSelectionStart() const { return Stb.select_start; } - int GetSelectionEnd() const { return Stb.select_end; } - void SelectAll() { Stb.select_start = 0; Stb.cursor = Stb.select_end = CurLenW; Stb.has_preferred_x = 0; } + void CursorAnimReset(); + void CursorClamp(); + bool HasSelection() const; + void ClearSelection(); + int GetCursorPos() const; + int GetSelectionStart() const; + int GetSelectionEnd() const; + void SelectAll(); // Reload user buf (WIP #2890) // If you modify underlying user-passed const char* while active you need to call this (InputText V2 may lift this) // strcpy(my_buf, "hello"); // if (ImGuiInputTextState* state = ImGui::GetInputTextState(id)) // id may be ImGui::GetItemID() is last item // state->ReloadUserBufAndSelectAll(); - void ReloadUserBufAndSelectAll() { ReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; } - void ReloadUserBufAndKeepSelection() { ReloadUserBuf = true; ReloadSelectionStart = Stb.select_start; ReloadSelectionEnd = Stb.select_end; } - void ReloadUserBufAndMoveToEnd() { ReloadUserBuf = true; ReloadSelectionStart = ReloadSelectionEnd = INT_MAX; } - + void ReloadUserBufAndSelectAll(); + void ReloadUserBufAndKeepSelection(); + void ReloadUserBufAndMoveToEnd(); }; enum ImGuiWindowRefreshFlags_ @@ -1177,6 +1197,9 @@ enum ImGuiNextWindowDataFlags_ ImGuiNextWindowDataFlags_HasScroll = 1 << 7, ImGuiNextWindowDataFlags_HasChildFlags = 1 << 8, ImGuiNextWindowDataFlags_HasRefreshPolicy = 1 << 9, + ImGuiNextWindowDataFlags_HasViewport = 1 << 10, + ImGuiNextWindowDataFlags_HasDock = 1 << 11, + ImGuiNextWindowDataFlags_HasWindowClass = 1 << 12, }; // Storage for SetNexWindow** functions @@ -1186,17 +1209,22 @@ struct ImGuiNextWindowData ImGuiCond PosCond; ImGuiCond SizeCond; ImGuiCond CollapsedCond; + ImGuiCond DockCond; ImVec2 PosVal; ImVec2 PosPivotVal; ImVec2 SizeVal; ImVec2 ContentSizeVal; ImVec2 ScrollVal; ImGuiChildFlags ChildFlags; + bool PosUndock; bool CollapsedVal; ImRect SizeConstraintRect; ImGuiSizeCallback SizeCallback; void* SizeCallbackUserData; float BgAlphaVal; // Override background alpha + ImGuiID ViewportId; + ImGuiID DockId; + ImGuiWindowClass WindowClass; ImVec2 MenuBarOffsetMinVal; // (Always on) This is not exposed publicly, so we don't clear it and it doesn't have a corresponding flag (could we? for consistency?) ImGuiWindowRefreshFlags RefreshFlagsVal; @@ -1204,10 +1232,6 @@ struct ImGuiNextWindowData inline void ClearFlags() { Flags = ImGuiNextWindowDataFlags_None; } }; -// Multi-Selection item index or identifier when using SetNextItemSelectionUserData()/BeginMultiSelect() -// (Most users are likely to use this store an item INDEX but this may be used to store a POINTER as well.) -typedef ImS64 ImGuiSelectionUserData; - enum ImGuiNextItemDataFlags_ { ImGuiNextItemDataFlags_None = 0, @@ -1215,13 +1239,15 @@ enum ImGuiNextItemDataFlags_ ImGuiNextItemDataFlags_HasOpen = 1 << 1, ImGuiNextItemDataFlags_HasShortcut = 1 << 2, ImGuiNextItemDataFlags_HasRefVal = 1 << 3, + ImGuiNextItemDataFlags_HasStorageID = 1 << 4, }; struct ImGuiNextItemData { - ImGuiNextItemDataFlags Flags; - ImGuiItemFlags ItemFlags; // Currently only tested/used for ImGuiItemFlags_AllowOverlap. + ImGuiNextItemDataFlags HasFlags; // Called HasFlags instead of Flags to avoid mistaking this + ImGuiItemFlags ItemFlags; // Currently only tested/used for ImGuiItemFlags_AllowOverlap and ImGuiItemFlags_HasSelectionUserData. // Non-flags members are NOT cleared by ItemAdd() meaning they are still valid during NavProcessItem() + ImGuiID FocusScopeId; // Set by SetNextItemSelectionUserData() ImGuiSelectionUserData SelectionUserData; // Set by SetNextItemSelectionUserData() (note that NULL/0 is a valid value, we use -1 == ImGuiSelectionUserData_Invalid to mark invalid values) float Width; // Set by SetNextItemWidth() ImGuiKeyChord Shortcut; // Set by SetNextItemShortcut() @@ -1229,16 +1255,17 @@ struct ImGuiNextItemData bool OpenVal; // Set by SetNextItemOpen() ImU8 OpenCond; // Set by SetNextItemOpen() ImGuiDataTypeStorage RefVal; // Not exposed yet, for ImGuiInputTextFlags_ParseEmptyAsRefVal + ImGuiID StorageId; // Set by SetNextItemStorageID() ImGuiNextItemData() { memset(this, 0, sizeof(*this)); SelectionUserData = -1; } - inline void ClearFlags() { Flags = ImGuiNextItemDataFlags_None; ItemFlags = ImGuiItemFlags_None; } // Also cleared manually by ItemAdd()! + inline void ClearFlags() { HasFlags = ImGuiNextItemDataFlags_None; ItemFlags = ImGuiItemFlags_None; } // Also cleared manually by ItemAdd()! }; // Status storage for the last submitted item struct ImGuiLastItemData { ImGuiID ID; - ImGuiItemFlags InFlags; // See ImGuiItemFlags_ + ImGuiItemFlags ItemFlags; // See ImGuiItemFlags_ ImGuiItemStatusFlags StatusFlags; // See ImGuiItemStatusFlags_ ImRect Rect; // Full rectangle ImRect NavRect; // Navigation scoring rectangle (not displayed) @@ -1250,19 +1277,24 @@ struct ImGuiLastItemData ImGuiLastItemData() { memset(this, 0, sizeof(*this)); } }; -// Store data emitted by TreeNode() for usage by TreePop() to implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere. -// This is the minimum amount of data that we need to perform the equivalent of NavApplyItemToResult() and which we can't infer in TreePop() -// Only stored when the node is a potential candidate for landing on a Left arrow jump. -struct ImGuiNavTreeNodeData +// Store data emitted by TreeNode() for usage by TreePop() +// - To implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere: store the minimum amount of data +// which we can't infer in TreePop(), to perform the equivalent of NavApplyItemToResult(). +// Only stored when the node is a potential candidate for landing on a Left arrow jump. +struct ImGuiTreeNodeStackData { ImGuiID ID; - ImGuiItemFlags InFlags; - ImRect NavRect; + ImGuiTreeNodeFlags TreeFlags; + ImGuiItemFlags ItemFlags; // Used for nav landing + ImRect NavRect; // Used for nav landing }; -struct IMGUI_API ImGuiStackSizes +// sizeof() = 20 +struct IMGUI_API ImGuiErrorRecoveryState { + short SizeOfWindowStack; short SizeOfIDStack; + short SizeOfTreeStack; short SizeOfColorStack; short SizeOfStyleVarStack; short SizeOfFontStack; @@ -1272,18 +1304,16 @@ struct IMGUI_API ImGuiStackSizes short SizeOfBeginPopupStack; short SizeOfDisabledStack; - ImGuiStackSizes() { memset(this, 0, sizeof(*this)); } - void SetToContextState(ImGuiContext* ctx); - void CompareWithContextState(ImGuiContext* ctx); + ImGuiErrorRecoveryState() { memset(this, 0, sizeof(*this)); } }; // Data saved for each window pushed into the stack struct ImGuiWindowStackData { - ImGuiWindow* Window; - ImGuiLastItemData ParentLastItemDataBackup; - ImGuiStackSizes StackSizesOnBegin; // Store size of various stacks for asserting - bool DisabledOverrideReenable; // Non-child window override disabled flag + ImGuiWindow* Window; + ImGuiLastItemData ParentLastItemDataBackup; + ImGuiErrorRecoveryState StackSizesInBegin; // Store size of various stacks for asserting + bool DisabledOverrideReenable; // Non-child window override disabled flag }; struct ImGuiShrinkWidthItem @@ -1352,8 +1382,8 @@ typedef ImBitArray ImBitAr #define ImGuiKey_NavKeyboardTweakFast ImGuiMod_Shift #define ImGuiKey_NavGamepadTweakSlow ImGuiKey_GamepadL1 #define ImGuiKey_NavGamepadTweakFast ImGuiKey_GamepadR1 -#define ImGuiKey_NavGamepadActivate ImGuiKey_GamepadFaceDown -#define ImGuiKey_NavGamepadCancel ImGuiKey_GamepadFaceRight +#define ImGuiKey_NavGamepadActivate (g.IO.ConfigNavSwapGamepadButtons ? ImGuiKey_GamepadFaceRight : ImGuiKey_GamepadFaceDown) +#define ImGuiKey_NavGamepadCancel (g.IO.ConfigNavSwapGamepadButtons ? ImGuiKey_GamepadFaceDown : ImGuiKey_GamepadFaceRight) #define ImGuiKey_NavGamepadMenu ImGuiKey_GamepadFaceLeft #define ImGuiKey_NavGamepadInput ImGuiKey_GamepadFaceUp @@ -1363,6 +1393,7 @@ enum ImGuiInputEventType ImGuiInputEventType_MousePos, ImGuiInputEventType_MouseWheel, ImGuiInputEventType_MouseButton, + ImGuiInputEventType_MouseViewport, ImGuiInputEventType_Key, ImGuiInputEventType_Text, ImGuiInputEventType_Focus, @@ -1383,6 +1414,7 @@ enum ImGuiInputSource struct ImGuiInputEventMousePos { float PosX, PosY; ImGuiMouseSource MouseSource; }; struct ImGuiInputEventMouseWheel { float WheelX, WheelY; ImGuiMouseSource MouseSource; }; struct ImGuiInputEventMouseButton { int Button; bool Down; ImGuiMouseSource MouseSource; }; +struct ImGuiInputEventMouseViewport { ImGuiID HoveredViewportID; }; struct ImGuiInputEventKey { ImGuiKey Key; bool Down; float AnalogValue; }; struct ImGuiInputEventText { unsigned int Char; }; struct ImGuiInputEventAppFocused { bool Focused; }; @@ -1397,6 +1429,7 @@ struct ImGuiInputEvent ImGuiInputEventMousePos MousePos; // if Type == ImGuiInputEventType_MousePos ImGuiInputEventMouseWheel MouseWheel; // if Type == ImGuiInputEventType_MouseWheel ImGuiInputEventMouseButton MouseButton; // if Type == ImGuiInputEventType_MouseButton + ImGuiInputEventMouseViewport MouseViewport; // if Type == ImGuiInputEventType_MouseViewport ImGuiInputEventKey Key; // if Type == ImGuiInputEventType_Key ImGuiInputEventText Text; // if Type == ImGuiInputEventType_Text ImGuiInputEventAppFocused AppFocused; // if Type == ImGuiInputEventType_Focus @@ -1551,12 +1584,18 @@ enum ImGuiScrollFlags_ ImGuiScrollFlags_MaskY_ = ImGuiScrollFlags_KeepVisibleEdgeY | ImGuiScrollFlags_KeepVisibleCenterY | ImGuiScrollFlags_AlwaysCenterY, }; -enum ImGuiNavHighlightFlags_ +enum ImGuiNavRenderCursorFlags_ { - ImGuiNavHighlightFlags_None = 0, - ImGuiNavHighlightFlags_Compact = 1 << 1, // Compact highlight, no padding - ImGuiNavHighlightFlags_AlwaysDraw = 1 << 2, // Draw rectangular highlight if (g.NavId == id) _even_ when using the mouse. - ImGuiNavHighlightFlags_NoRounding = 1 << 3, + ImGuiNavRenderCursorFlags_None = 0, + ImGuiNavRenderCursorFlags_Compact = 1 << 1, // Compact highlight, no padding/distance from focused item + ImGuiNavRenderCursorFlags_AlwaysDraw = 1 << 2, // Draw rectangular highlight if (g.NavId == id) even when g.NavCursorVisible == false, aka even when using the mouse. + ImGuiNavRenderCursorFlags_NoRounding = 1 << 3, +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiNavHighlightFlags_None = ImGuiNavRenderCursorFlags_None, // Renamed in 1.91.4 + ImGuiNavHighlightFlags_Compact = ImGuiNavRenderCursorFlags_Compact, // Renamed in 1.91.4 + ImGuiNavHighlightFlags_AlwaysDraw = ImGuiNavRenderCursorFlags_AlwaysDraw, // Renamed in 1.91.4 + ImGuiNavHighlightFlags_NoRounding = ImGuiNavRenderCursorFlags_NoRounding, // Renamed in 1.91.4 +#endif }; enum ImGuiNavMoveFlags_ @@ -1577,7 +1616,7 @@ enum ImGuiNavMoveFlags_ ImGuiNavMoveFlags_IsPageMove = 1 << 11, // Identify a PageDown/PageUp request. ImGuiNavMoveFlags_Activate = 1 << 12, // Activate/select target item. ImGuiNavMoveFlags_NoSelect = 1 << 13, // Don't trigger selection by not setting g.NavJustMovedTo - ImGuiNavMoveFlags_NoSetNavHighlight = 1 << 14, // Do not alter the visible state of keyboard vs mouse nav highlight + ImGuiNavMoveFlags_NoSetNavCursorVisible = 1 << 14, // Do not alter the nav cursor visible state ImGuiNavMoveFlags_NoClearActiveId = 1 << 15, // (Experimental) Do not clear active id when applying move result }; @@ -1595,17 +1634,17 @@ struct ImGuiNavItemData ImGuiID ID; // Init,Move // Best candidate item ID ImGuiID FocusScopeId; // Init,Move // Best candidate focus scope ID ImRect RectRel; // Init,Move // Best candidate bounding box in window relative space - ImGuiItemFlags InFlags; // ????,Move // Best candidate item flags + ImGuiItemFlags ItemFlags; // ????,Move // Best candidate item flags float DistBox; // Move // Best candidate box distance to current NavId float DistCenter; // Move // Best candidate center distance to current NavId float DistAxial; // Move // Best candidate axial distance to current NavId - ImGuiSelectionUserData SelectionUserData;//I+Mov // Best candidate SetNextItemSelectionData() value. + ImGuiSelectionUserData SelectionUserData;//I+Mov // Best candidate SetNextItemSelectionUserData() value. Valid if (ItemFlags & ImGuiItemFlags_HasSelectionUserData) ImGuiNavItemData() { Clear(); } - void Clear() { Window = NULL; ID = FocusScopeId = 0; InFlags = 0; SelectionUserData = -1; DistBox = DistCenter = DistAxial = FLT_MAX; } + void Clear() { Window = NULL; ID = FocusScopeId = 0; ItemFlags = 0; SelectionUserData = -1; DistBox = DistCenter = DistAxial = FLT_MAX; } }; -// Storage for PushFocusScope() +// Storage for PushFocusScope(), g.FocusScopeStack[], g.NavFocusRoute[] struct ImGuiFocusScopeData { ImGuiID ID; @@ -1705,6 +1744,34 @@ struct ImGuiOldColumns ImGuiOldColumns() { memset(this, 0, sizeof(*this)); } }; +//----------------------------------------------------------------------------- +// [SECTION] Box-select support +//----------------------------------------------------------------------------- + +struct ImGuiBoxSelectState +{ + // Active box-selection data (persistent, 1 active at a time) + ImGuiID ID; + bool IsActive; + bool IsStarting; + bool IsStartedFromVoid; // Starting click was not from an item. + bool IsStartedSetNavIdOnce; + bool RequestClear; + ImGuiKeyChord KeyMods : 16; // Latched key-mods for box-select logic. + ImVec2 StartPosRel; // Start position in window-contents relative space (to support scrolling) + ImVec2 EndPosRel; // End position in window-contents relative space + ImVec2 ScrollAccum; // Scrolling accumulator (to behave at high-frame spaces) + ImGuiWindow* Window; + + // Temporary/Transient data + bool UnclipMode; // (Temp/Transient, here in hot area). Set/cleared by the BeginMultiSelect()/EndMultiSelect() owning active box-select. + ImRect UnclipRect; // Rectangle where ItemAdd() clipping may be temporarily disabled. Need support by multi-select supporting widgets. + ImRect BoxSelectRectPrev; // Selection rectangle in absolute coordinates (derived every frame from BoxSelectStartPosRel and MousePos) + ImRect BoxSelectRectCurr; + + ImGuiBoxSelectState() { memset(this, 0, sizeof(*this)); } +}; + //----------------------------------------------------------------------------- // [SECTION] Multi-select support //----------------------------------------------------------------------------- @@ -1712,16 +1779,199 @@ struct ImGuiOldColumns // We always assume that -1 is an invalid value (which works for indices and pointers) #define ImGuiSelectionUserData_Invalid ((ImGuiSelectionUserData)-1) -#ifdef IMGUI_HAS_MULTI_SELECT -// -#endif // #ifdef IMGUI_HAS_MULTI_SELECT +// Temporary storage for multi-select +struct IMGUI_API ImGuiMultiSelectTempData +{ + ImGuiMultiSelectIO IO; // MUST BE FIRST FIELD. Requests are set and returned by BeginMultiSelect()/EndMultiSelect() + written to by user during the loop. + ImGuiMultiSelectState* Storage; + ImGuiID FocusScopeId; // Copied from g.CurrentFocusScopeId (unless another selection scope was pushed manually) + ImGuiMultiSelectFlags Flags; + ImVec2 ScopeRectMin; + ImVec2 BackupCursorMaxPos; + ImGuiSelectionUserData LastSubmittedItem; // Copy of last submitted item data, used to merge output ranges. + ImGuiID BoxSelectId; + ImGuiKeyChord KeyMods; + ImS8 LoopRequestSetAll; // -1: no operation, 0: clear all, 1: select all. + bool IsEndIO; // Set when switching IO from BeginMultiSelect() to EndMultiSelect() state. + bool IsFocused; // Set if currently focusing the selection scope (any item of the selection). May be used if you have custom shortcut associated to selection. + bool IsKeyboardSetRange; // Set by BeginMultiSelect() when using Shift+Navigation. Because scrolling may be affected we can't afford a frame of lag with Shift+Navigation. + bool NavIdPassedBy; + bool RangeSrcPassedBy; // Set by the item that matches RangeSrcItem. + bool RangeDstPassedBy; // Set by the item that matches NavJustMovedToId when IsSetRange is set. + + ImGuiMultiSelectTempData() { Clear(); } + void Clear() { size_t io_sz = sizeof(IO); ClearIO(); memset((void*)(&IO + 1), 0, sizeof(*this) - io_sz); } // Zero-clear except IO as we preserve IO.Requests[] buffer allocation. + void ClearIO() { IO.Requests.resize(0); IO.RangeSrcItem = IO.NavIdItem = ImGuiSelectionUserData_Invalid; IO.NavIdSelected = IO.RangeSrcReset = false; } +}; + +// Persistent storage for multi-select (as long as selection is alive) +struct IMGUI_API ImGuiMultiSelectState +{ + ImGuiWindow* Window; + ImGuiID ID; + int LastFrameActive; // Last used frame-count, for GC. + int LastSelectionSize; // Set by BeginMultiSelect() based on optional info provided by user. May be -1 if unknown. + ImS8 RangeSelected; // -1 (don't have) or true/false + ImS8 NavIdSelected; // -1 (don't have) or true/false + ImGuiSelectionUserData RangeSrcItem; // + ImGuiSelectionUserData NavIdItem; // SetNextItemSelectionUserData() value for NavId (if part of submitted items) + + ImGuiMultiSelectState() { Window = NULL; ID = 0; LastFrameActive = LastSelectionSize = 0; RangeSelected = NavIdSelected = -1; RangeSrcItem = NavIdItem = ImGuiSelectionUserData_Invalid; } +}; //----------------------------------------------------------------------------- // [SECTION] Docking support //----------------------------------------------------------------------------- +#define DOCKING_HOST_DRAW_CHANNEL_BG 0 // Dock host: background fill +#define DOCKING_HOST_DRAW_CHANNEL_FG 1 // Dock host: decorations and contents + #ifdef IMGUI_HAS_DOCK -// + +// Extend ImGuiDockNodeFlags_ +enum ImGuiDockNodeFlagsPrivate_ +{ + // [Internal] + ImGuiDockNodeFlags_DockSpace = 1 << 10, // Saved // A dockspace is a node that occupy space within an existing user window. Otherwise the node is floating and create its own window. + ImGuiDockNodeFlags_CentralNode = 1 << 11, // Saved // The central node has 2 main properties: stay visible when empty, only use "remaining" spaces from its neighbor. + ImGuiDockNodeFlags_NoTabBar = 1 << 12, // Saved // Tab bar is completely unavailable. No triangle in the corner to enable it back. + ImGuiDockNodeFlags_HiddenTabBar = 1 << 13, // Saved // Tab bar is hidden, with a triangle in the corner to show it again (NB: actual tab-bar instance may be destroyed as this is only used for single-window tab bar) + ImGuiDockNodeFlags_NoWindowMenuButton = 1 << 14, // Saved // Disable window/docking menu (that one that appears instead of the collapse button) + ImGuiDockNodeFlags_NoCloseButton = 1 << 15, // Saved // Disable close button + ImGuiDockNodeFlags_NoResizeX = 1 << 16, // // + ImGuiDockNodeFlags_NoResizeY = 1 << 17, // // + ImGuiDockNodeFlags_DockedWindowsInFocusRoute= 1 << 18, // // Any docked window will be automatically be focus-route chained (window->ParentWindowForFocusRoute set to this) so Shortcut() in this window can run when any docked window is focused. + + // Disable docking/undocking actions in this dockspace or individual node (existing docked nodes will be preserved) + // Those are not exposed in public because the desirable sharing/inheriting/copy-flag-on-split behaviors are quite difficult to design and understand. + // The two public flags ImGuiDockNodeFlags_NoDockingOverCentralNode/ImGuiDockNodeFlags_NoDockingSplit don't have those issues. + ImGuiDockNodeFlags_NoDockingSplitOther = 1 << 19, // // Disable this node from splitting other windows/nodes. + ImGuiDockNodeFlags_NoDockingOverMe = 1 << 20, // // Disable other windows/nodes from being docked over this node. + ImGuiDockNodeFlags_NoDockingOverOther = 1 << 21, // // Disable this node from being docked over another window or non-empty node. + ImGuiDockNodeFlags_NoDockingOverEmpty = 1 << 22, // // Disable this node from being docked over an empty node (e.g. DockSpace with no other windows) + ImGuiDockNodeFlags_NoDocking = ImGuiDockNodeFlags_NoDockingOverMe | ImGuiDockNodeFlags_NoDockingOverOther | ImGuiDockNodeFlags_NoDockingOverEmpty | ImGuiDockNodeFlags_NoDockingSplit | ImGuiDockNodeFlags_NoDockingSplitOther, + + // Masks + ImGuiDockNodeFlags_SharedFlagsInheritMask_ = ~0, + ImGuiDockNodeFlags_NoResizeFlagsMask_ = (int)ImGuiDockNodeFlags_NoResize | ImGuiDockNodeFlags_NoResizeX | ImGuiDockNodeFlags_NoResizeY, + + // When splitting, those local flags are moved to the inheriting child, never duplicated + ImGuiDockNodeFlags_LocalFlagsTransferMask_ = (int)ImGuiDockNodeFlags_NoDockingSplit | ImGuiDockNodeFlags_NoResizeFlagsMask_ | (int)ImGuiDockNodeFlags_AutoHideTabBar | ImGuiDockNodeFlags_CentralNode | ImGuiDockNodeFlags_NoTabBar | ImGuiDockNodeFlags_HiddenTabBar | ImGuiDockNodeFlags_NoWindowMenuButton | ImGuiDockNodeFlags_NoCloseButton, + ImGuiDockNodeFlags_SavedFlagsMask_ = ImGuiDockNodeFlags_NoResizeFlagsMask_ | ImGuiDockNodeFlags_DockSpace | ImGuiDockNodeFlags_CentralNode | ImGuiDockNodeFlags_NoTabBar | ImGuiDockNodeFlags_HiddenTabBar | ImGuiDockNodeFlags_NoWindowMenuButton | ImGuiDockNodeFlags_NoCloseButton, +}; + +// Store the source authority (dock node vs window) of a field +enum ImGuiDataAuthority_ +{ + ImGuiDataAuthority_Auto, + ImGuiDataAuthority_DockNode, + ImGuiDataAuthority_Window, +}; + +enum ImGuiDockNodeState +{ + ImGuiDockNodeState_Unknown, + ImGuiDockNodeState_HostWindowHiddenBecauseSingleWindow, + ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing, + ImGuiDockNodeState_HostWindowVisible, +}; + +// sizeof() 156~192 +struct IMGUI_API ImGuiDockNode +{ + ImGuiID ID; + ImGuiDockNodeFlags SharedFlags; // (Write) Flags shared by all nodes of a same dockspace hierarchy (inherited from the root node) + ImGuiDockNodeFlags LocalFlags; // (Write) Flags specific to this node + ImGuiDockNodeFlags LocalFlagsInWindows; // (Write) Flags specific to this node, applied from windows + ImGuiDockNodeFlags MergedFlags; // (Read) Effective flags (== SharedFlags | LocalFlagsInNode | LocalFlagsInWindows) + ImGuiDockNodeState State; + ImGuiDockNode* ParentNode; + ImGuiDockNode* ChildNodes[2]; // [Split node only] Child nodes (left/right or top/bottom). Consider switching to an array. + ImVector Windows; // Note: unordered list! Iterate TabBar->Tabs for user-order. + ImGuiTabBar* TabBar; + ImVec2 Pos; // Current position + ImVec2 Size; // Current size + ImVec2 SizeRef; // [Split node only] Last explicitly written-to size (overridden when using a splitter affecting the node), used to calculate Size. + ImGuiAxis SplitAxis; // [Split node only] Split axis (X or Y) + ImGuiWindowClass WindowClass; // [Root node only] + ImU32 LastBgColor; + + ImGuiWindow* HostWindow; + ImGuiWindow* VisibleWindow; // Generally point to window which is ID is == SelectedTabID, but when CTRL+Tabbing this can be a different window. + ImGuiDockNode* CentralNode; // [Root node only] Pointer to central node. + ImGuiDockNode* OnlyNodeWithWindows; // [Root node only] Set when there is a single visible node within the hierarchy. + int CountNodeWithWindows; // [Root node only] + int LastFrameAlive; // Last frame number the node was updated or kept alive explicitly with DockSpace() + ImGuiDockNodeFlags_KeepAliveOnly + int LastFrameActive; // Last frame number the node was updated. + int LastFrameFocused; // Last frame number the node was focused. + ImGuiID LastFocusedNodeId; // [Root node only] Which of our child docking node (any ancestor in the hierarchy) was last focused. + ImGuiID SelectedTabId; // [Leaf node only] Which of our tab/window is selected. + ImGuiID WantCloseTabId; // [Leaf node only] Set when closing a specific tab/window. + ImGuiID RefViewportId; // Reference viewport ID from visible window when HostWindow == NULL. + ImGuiDataAuthority AuthorityForPos :3; + ImGuiDataAuthority AuthorityForSize :3; + ImGuiDataAuthority AuthorityForViewport :3; + bool IsVisible :1; // Set to false when the node is hidden (usually disabled as it has no active window) + bool IsFocused :1; + bool IsBgDrawnThisFrame :1; + bool HasCloseButton :1; // Provide space for a close button (if any of the docked window has one). Note that button may be hidden on window without one. + bool HasWindowMenuButton :1; + bool HasCentralNodeChild :1; + bool WantCloseAll :1; // Set when closing all tabs at once. + bool WantLockSizeOnce :1; + bool WantMouseMove :1; // After a node extraction we need to transition toward moving the newly created host window + bool WantHiddenTabBarUpdate :1; + bool WantHiddenTabBarToggle :1; + + ImGuiDockNode(ImGuiID id); + ~ImGuiDockNode(); + bool IsRootNode() const { return ParentNode == NULL; } + bool IsDockSpace() const { return (MergedFlags & ImGuiDockNodeFlags_DockSpace) != 0; } + bool IsFloatingNode() const { return ParentNode == NULL && (MergedFlags & ImGuiDockNodeFlags_DockSpace) == 0; } + bool IsCentralNode() const { return (MergedFlags & ImGuiDockNodeFlags_CentralNode) != 0; } + bool IsHiddenTabBar() const { return (MergedFlags & ImGuiDockNodeFlags_HiddenTabBar) != 0; } // Hidden tab bar can be shown back by clicking the small triangle + bool IsNoTabBar() const { return (MergedFlags & ImGuiDockNodeFlags_NoTabBar) != 0; } // Never show a tab bar + bool IsSplitNode() const { return ChildNodes[0] != NULL; } + bool IsLeafNode() const { return ChildNodes[0] == NULL; } + bool IsEmpty() const { return ChildNodes[0] == NULL && Windows.Size == 0; } + ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } + + void SetLocalFlags(ImGuiDockNodeFlags flags) { LocalFlags = flags; UpdateMergedFlags(); } + void UpdateMergedFlags() { MergedFlags = SharedFlags | LocalFlags | LocalFlagsInWindows; } +}; + +// List of colors that are stored at the time of Begin() into Docked Windows. +// We currently store the packed colors in a simple array window->DockStyle.Colors[]. +// A better solution may involve appending into a log of colors in ImGuiContext + store offsets into those arrays in ImGuiWindow, +// but it would be more complex as we'd need to double-buffer both as e.g. drop target may refer to window from last frame. +enum ImGuiWindowDockStyleCol +{ + ImGuiWindowDockStyleCol_Text, + ImGuiWindowDockStyleCol_TabHovered, + ImGuiWindowDockStyleCol_TabFocused, + ImGuiWindowDockStyleCol_TabSelected, + ImGuiWindowDockStyleCol_TabSelectedOverline, + ImGuiWindowDockStyleCol_TabDimmed, + ImGuiWindowDockStyleCol_TabDimmedSelected, + ImGuiWindowDockStyleCol_TabDimmedSelectedOverline, + ImGuiWindowDockStyleCol_COUNT +}; + +// We don't store style.Alpha: dock_node->LastBgColor embeds it and otherwise it would only affect the docking tab, which intuitively I would say we don't want to. +struct ImGuiWindowDockStyle +{ + ImU32 Colors[ImGuiWindowDockStyleCol_COUNT]; +}; + +struct ImGuiDockContext +{ + ImGuiStorage Nodes; // Map ID -> ImGuiDockNode*: Active nodes + ImVector Requests; + ImVector NodesSettings; + bool WantFullRebuild; + ImGuiDockContext() { memset(this, 0, sizeof(*this)); } +}; + #endif // #ifdef IMGUI_HAS_DOCK //----------------------------------------------------------------------------- @@ -1732,27 +1982,47 @@ struct ImGuiOldColumns // Every instance of ImGuiViewport is in fact a ImGuiViewportP. struct ImGuiViewportP : public ImGuiViewport { + ImGuiWindow* Window; // Set when the viewport is owned by a window (and ImGuiViewportFlags_CanHostOtherWindows is NOT set) + int Idx; + int LastFrameActive; // Last frame number this viewport was activated by a window + int LastFocusedStampCount; // Last stamp number from when a window hosted by this viewport was focused (by comparing this value between two viewport we have an implicit viewport z-order we use as fallback) + ImGuiID LastNameHash; + ImVec2 LastPos; + ImVec2 LastSize; + float Alpha; // Window opacity (when dragging dockable windows/viewports we make them transparent) + float LastAlpha; + bool LastFocusedHadNavWindow;// Instead of maintaining a LastFocusedWindow (which may harder to correctly maintain), we merely store weither NavWindow != NULL last time the viewport was focused. + short PlatformMonitor; int BgFgDrawListsLastFrame[2]; // Last frame number the background (0) and foreground (1) draw lists were used ImDrawList* BgFgDrawLists[2]; // Convenience background (0) and foreground (1) draw lists. We use them to draw software mouser cursor when io.MouseDrawCursor is set and to draw most debug overlays. ImDrawData DrawDataP; ImDrawDataBuilder DrawDataBuilder; // Temporary data while building final ImDrawData - ImVec2 WorkOffsetMin; // Work Area: Offset from Pos to top-left corner of Work Area. Generally (0,0) or (0,+main_menu_bar_height). Work Area is Full Area but without menu-bars/status-bars (so WorkArea always fit inside Pos/Size!) - ImVec2 WorkOffsetMax; // Work Area: Offset from Pos+Size to bottom-right corner of Work Area. Generally (0,0) or (0,-status_bar_height). - ImVec2 BuildWorkOffsetMin; // Work Area: Offset being built during current frame. Generally >= 0.0f. - ImVec2 BuildWorkOffsetMax; // Work Area: Offset being built during current frame. Generally <= 0.0f. - - ImGuiViewportP() { BgFgDrawListsLastFrame[0] = BgFgDrawListsLastFrame[1] = -1; BgFgDrawLists[0] = BgFgDrawLists[1] = NULL; } - ~ImGuiViewportP() { if (BgFgDrawLists[0]) IM_DELETE(BgFgDrawLists[0]); if (BgFgDrawLists[1]) IM_DELETE(BgFgDrawLists[1]); } + ImVec2 LastPlatformPos; + ImVec2 LastPlatformSize; + ImVec2 LastRendererSize; + + // Per-viewport work area + // - Insets are >= 0.0f values, distance from viewport corners to work area. + // - BeginMainMenuBar() and DockspaceOverViewport() tend to use work area to avoid stepping over existing contents. + // - Generally 'safeAreaInsets' in iOS land, 'DisplayCutout' in Android land. + ImVec2 WorkInsetMin; // Work Area inset locked for the frame. GetWorkRect() always fits within GetMainRect(). + ImVec2 WorkInsetMax; // " + ImVec2 BuildWorkInsetMin; // Work Area inset accumulator for current frame, to become next frame's WorkInset + ImVec2 BuildWorkInsetMax; // " + + ImGuiViewportP() { Window = NULL; Idx = -1; LastFrameActive = BgFgDrawListsLastFrame[0] = BgFgDrawListsLastFrame[1] = LastFocusedStampCount = -1; LastNameHash = 0; Alpha = LastAlpha = 1.0f; LastFocusedHadNavWindow = false; PlatformMonitor = -1; BgFgDrawLists[0] = BgFgDrawLists[1] = NULL; LastPlatformPos = LastPlatformSize = LastRendererSize = ImVec2(FLT_MAX, FLT_MAX); } + ~ImGuiViewportP() { if (BgFgDrawLists[0]) IM_DELETE(BgFgDrawLists[0]); if (BgFgDrawLists[1]) IM_DELETE(BgFgDrawLists[1]); } + void ClearRequestFlags() { PlatformRequestClose = PlatformRequestMove = PlatformRequestResize = false; } // Calculate work rect pos/size given a set of offset (we have 1 pair of offset for rect locked from last frame data, and 1 pair for currently building rect) - ImVec2 CalcWorkRectPos(const ImVec2& off_min) const { return ImVec2(Pos.x + off_min.x, Pos.y + off_min.y); } - ImVec2 CalcWorkRectSize(const ImVec2& off_min, const ImVec2& off_max) const { return ImVec2(ImMax(0.0f, Size.x - off_min.x + off_max.x), ImMax(0.0f, Size.y - off_min.y + off_max.y)); } - void UpdateWorkRect() { WorkPos = CalcWorkRectPos(WorkOffsetMin); WorkSize = CalcWorkRectSize(WorkOffsetMin, WorkOffsetMax); } // Update public fields + ImVec2 CalcWorkRectPos(const ImVec2& inset_min) const { return ImVec2(Pos.x + inset_min.x, Pos.y + inset_min.y); } + ImVec2 CalcWorkRectSize(const ImVec2& inset_min, const ImVec2& inset_max) const { return ImVec2(ImMax(0.0f, Size.x - inset_min.x - inset_max.x), ImMax(0.0f, Size.y - inset_min.y - inset_max.y)); } + void UpdateWorkRect() { WorkPos = CalcWorkRectPos(WorkInsetMin); WorkSize = CalcWorkRectSize(WorkInsetMin, WorkInsetMax); } // Update public fields // Helpers to retrieve ImRect (we don't need to store BuildWorkRect as every access tend to change it, hence the code asymmetry) ImRect GetMainRect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } ImRect GetWorkRect() const { return ImRect(WorkPos.x, WorkPos.y, WorkPos.x + WorkSize.x, WorkPos.y + WorkSize.y); } - ImRect GetBuildWorkRect() const { ImVec2 pos = CalcWorkRectPos(BuildWorkOffsetMin); ImVec2 size = CalcWorkRectSize(BuildWorkOffsetMin, BuildWorkOffsetMax); return ImRect(pos.x, pos.y, pos.x + size.x, pos.y + size.y); } + ImRect GetBuildWorkRect() const { ImVec2 pos = CalcWorkRectPos(BuildWorkInsetMin); ImVec2 size = CalcWorkRectSize(BuildWorkInsetMin, BuildWorkInsetMax); return ImRect(pos.x, pos.y, pos.x + size.x, pos.y + size.y); } }; //----------------------------------------------------------------------------- @@ -1765,14 +2035,19 @@ struct ImGuiViewportP : public ImGuiViewport struct ImGuiWindowSettings { ImGuiID ID; - ImVec2ih Pos; + ImVec2ih Pos; // NB: Settings position are stored RELATIVE to the viewport! Whereas runtime ones are absolute positions. ImVec2ih Size; + ImVec2ih ViewportPos; + ImGuiID ViewportId; + ImGuiID DockId; // ID of last known DockNode (even if the DockNode is invisible because it has only 1 active window), or 0 if none. + ImGuiID ClassId; // ID of window class if specified + short DockOrder; // Order of the last time the window was visible within its DockNode. This is used to reorder windows that are reappearing on the same frame. Same value between windows that were active and windows that were none are possible. bool Collapsed; bool IsChild; bool WantApply; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) bool WantDelete; // Set to invalidate/delete the settings entry - ImGuiWindowSettings() { memset(this, 0, sizeof(*this)); } + ImGuiWindowSettings() { memset(this, 0, sizeof(*this)); DockOrder = -1; } char* GetName() { return (char*)(this + 1); } }; @@ -1806,6 +2081,11 @@ enum ImGuiLocKey : int ImGuiLocKey_WindowingMainMenuBar, ImGuiLocKey_WindowingPopup, ImGuiLocKey_WindowingUntitled, + ImGuiLocKey_OpenLink_s, + ImGuiLocKey_CopyLink, + ImGuiLocKey_DockingHideTabBar, + ImGuiLocKey_DockingHoldShiftToDock, + ImGuiLocKey_DockingDragToUndockOrMoveNode, ImGuiLocKey_COUNT }; @@ -1815,25 +2095,45 @@ struct ImGuiLocEntry const char* Text; }; +//----------------------------------------------------------------------------- +// [SECTION] Error handling, State recovery support +//----------------------------------------------------------------------------- + +// Macros used by Recoverable Error handling +// - Only dispatch error if _EXPR: evaluate as assert (similar to an assert macro). +// - The message will always be a string literal, in order to increase likelihood of being display by an assert handler. +// - See 'Demo->Configuration->Error Handling' and ImGuiIO definitions for details on error handling. +// - Read https://github.com/ocornut/imgui/wiki/Error-Handling for details on error handling. +#ifndef IM_ASSERT_USER_ERROR +#define IM_ASSERT_USER_ERROR(_EXPR,_MSG) do { if (!(_EXPR) && ImGui::ErrorLog(_MSG)) { IM_ASSERT((_EXPR) && _MSG); } } while (0) // Recoverable User Error +#endif + +// The error callback is currently not public, as it is expected that only advanced users will rely on it. +typedef void (*ImGuiErrorCallback)(ImGuiContext* ctx, void* user_data, const char* msg); // Function signature for g.ErrorCallback //----------------------------------------------------------------------------- // [SECTION] Metrics, Debug Tools //----------------------------------------------------------------------------- +// See IMGUI_DEBUG_LOG() and IMGUI_DEBUG_LOG_XXX() macros. enum ImGuiDebugLogFlags_ { // Event types ImGuiDebugLogFlags_None = 0, - ImGuiDebugLogFlags_EventActiveId = 1 << 0, - ImGuiDebugLogFlags_EventFocus = 1 << 1, - ImGuiDebugLogFlags_EventPopup = 1 << 2, - ImGuiDebugLogFlags_EventNav = 1 << 3, - ImGuiDebugLogFlags_EventClipper = 1 << 4, - ImGuiDebugLogFlags_EventSelection = 1 << 5, - ImGuiDebugLogFlags_EventIO = 1 << 6, - ImGuiDebugLogFlags_EventInputRouting = 1 << 7, - - ImGuiDebugLogFlags_EventMask_ = ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventSelection | ImGuiDebugLogFlags_EventIO | ImGuiDebugLogFlags_EventInputRouting, + ImGuiDebugLogFlags_EventError = 1 << 0, // Error submitted by IM_ASSERT_USER_ERROR() + ImGuiDebugLogFlags_EventActiveId = 1 << 1, + ImGuiDebugLogFlags_EventFocus = 1 << 2, + ImGuiDebugLogFlags_EventPopup = 1 << 3, + ImGuiDebugLogFlags_EventNav = 1 << 4, + ImGuiDebugLogFlags_EventClipper = 1 << 5, + ImGuiDebugLogFlags_EventSelection = 1 << 6, + ImGuiDebugLogFlags_EventIO = 1 << 7, + ImGuiDebugLogFlags_EventFont = 1 << 8, + ImGuiDebugLogFlags_EventInputRouting = 1 << 9, + ImGuiDebugLogFlags_EventDocking = 1 << 10, + ImGuiDebugLogFlags_EventViewport = 1 << 11, + + ImGuiDebugLogFlags_EventMask_ = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventSelection | ImGuiDebugLogFlags_EventIO | ImGuiDebugLogFlags_EventFont | ImGuiDebugLogFlags_EventInputRouting | ImGuiDebugLogFlags_EventDocking | ImGuiDebugLogFlags_EventViewport, ImGuiDebugLogFlags_OutputToTTY = 1 << 20, // Also send output to TTY ImGuiDebugLogFlags_OutputToTestEngine = 1 << 21, // Also send output to Test Engine }; @@ -1866,6 +2166,7 @@ struct ImGuiMetricsConfig bool ShowDrawCmdBoundingBoxes = true; bool ShowTextEncodingViewer = false; bool ShowAtlasTintedWithTextColor = false; + bool ShowDockingNodes = false; int ShowWindowsRectsType = -1; int ShowTablesRectsType = -1; int HighlightMonitorIdx = -1; @@ -1923,15 +2224,20 @@ struct ImGuiContext bool Initialized; bool FontAtlasOwnedByContext; // IO.Fonts-> is owned by the ImGuiContext and will be destructed along with it. ImGuiIO IO; + ImGuiPlatformIO PlatformIO; ImGuiStyle Style; + ImGuiConfigFlags ConfigFlagsCurrFrame; // = g.IO.ConfigFlags at the time of NewFrame() + ImGuiConfigFlags ConfigFlagsLastFrame; ImFont* Font; // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back() float FontSize; // (Shortcut) == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height for current window. float FontBaseSize; // (Shortcut) == IO.FontGlobalScale * Font->Scale * Font->FontSize. Base text height. - float CurrentDpiScale; // Current window/viewport DpiScale + float FontScale; // == FontSize / Font->FontSize + float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale ImDrawListSharedData DrawListSharedData; double Time; int FrameCount; int FrameCountEnded; + int FrameCountPlatformEnded; int FrameCountRendered; bool WithinFrameScope; // Set by NewFrame(), cleared by EndFrame() bool WithinFrameScopeWithImplicitWindow; // Set by NewFrame(), cleared by EndFrame() when the implicit debug window has been pushed @@ -1939,6 +2245,7 @@ struct ImGuiContext bool GcCompactAll; // Request full GC bool TestEngineHookItems; // Will call test engine hooks: ImGuiTestEngineHook_ItemAdd(), ImGuiTestEngineHook_ItemInfo(), ImGuiTestEngineHook_Log() void* TestEngine; // Test engine user data + char ContextName[16]; // Storage for a context name (to facilitate debugging multi-context setups) // Inputs ImVector InputEventsQueue; // Input events which will be trickled/written into IO structure. @@ -1958,7 +2265,8 @@ struct ImGuiContext ImGuiWindow* CurrentWindow; // Window being drawn into ImGuiWindow* HoveredWindow; // Window the mouse is hovering. Will typically catch mouse inputs. ImGuiWindow* HoveredWindowUnderMovingWindow; // Hovered window ignoring MovingWindow. Only set if MovingWindow is set. - ImGuiWindow* MovingWindow; // Track the window we clicked on (in order to preserve focus). The actual window that is moved is generally MovingWindow->RootWindow. + ImGuiWindow* HoveredWindowBeforeClear; // Window the mouse is hovering. Filled even with _NoMouse. This is currently useful for multi-context compositors. + ImGuiWindow* MovingWindow; // Track the window we clicked on (in order to preserve focus). The actual window that is moved is generally MovingWindow->RootWindowDockTree. ImGuiWindow* WheelingWindow; // Track the window we started mouse-wheeling on. Until a timer elapse or mouse has moved, generally keep scrolling the same window even if during the course of scrolling the mouse ends up hovering a child window. ImVec2 WheelingWindowRefMousePos; int WheelingWindowStartFrame; // This may be set one frame before WheelingWindow is != NULL @@ -1968,9 +2276,11 @@ struct ImGuiContext ImVec2 WheelingAxisAvg; // Item/widgets state and tracking information + ImGuiID DebugDrawIdConflicts; // Set when we detect multiple items with the same identifier ImGuiID DebugHookIdInfo; // Will call core hooks: DebugHookIdInfo() from GetID functions, used by ID Stack Tool [next HoveredId/ActiveId to not pull in an extra cache-line] ImGuiID HoveredId; // Hovered widget, filled during the frame ImGuiID HoveredIdPreviousFrame; + int HoveredIdPreviousFrameItemCount; // Count numbers of items using the same ID as last frame's hovered id float HoveredIdTimer; // Measure contiguous hovering time float HoveredIdNotActiveTimer; // Measure contiguous hovering time where the item has not been active bool HoveredIdAllowOverlap; @@ -2008,11 +2318,9 @@ struct ImGuiContext ImGuiKeyOwnerData KeysOwnerData[ImGuiKey_NamedKey_COUNT]; ImGuiKeyRoutingTable KeysRoutingTable; ImU32 ActiveIdUsingNavDirMask; // Active widget will want to read those nav move requests (e.g. can activate a button and move away from it) - bool ActiveIdUsingAllKeyboardKeys; // Active widget will want to read all keyboard keys inputs. (FIXME: This is a shortcut for not taking ownership of 100+ keys but perhaps best to not have the inconsistency) + bool ActiveIdUsingAllKeyboardKeys; // Active widget will want to read all keyboard keys inputs. (this is a shortcut for not taking ownership of 100+ keys, frequently used by drag operations) ImGuiKeyChord DebugBreakInShortcutRouting; // Set to break in SetShortcutRouting()/Shortcut() calls. -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - ImU32 ActiveIdUsingNavInputMask; // If you used this. Since (IMGUI_VERSION_NUM >= 18804) : 'g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel);' becomes 'SetKeyOwner(ImGuiKey_Escape, g.ActiveId) and/or SetKeyOwner(ImGuiKey_NavGamepadCancel, g.ActiveId);' -#endif + //ImU32 ActiveIdUsingNavInputMask; // [OBSOLETE] Since (IMGUI_VERSION_NUM >= 18804) : 'g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel);' becomes --> 'SetKeyOwner(ImGuiKey_Escape, g.ActiveId) and/or SetKeyOwner(ImGuiKey_NavGamepadCancel, g.ActiveId);' // Next window/item data ImGuiID CurrentFocusScopeId; // Value for currently appending items == g.FocusScopeStack.back(). Not to be mistaken with g.NavFocusScopeId. @@ -2033,15 +2341,31 @@ struct ImGuiContext ImVector GroupStack; // Stack for BeginGroup()/EndGroup() - not inherited by Begin() ImVector OpenPopupStack; // Which popups are open (persistent) ImVector BeginPopupStack; // Which level of BeginPopup() we are in (reset every frame) - ImVector NavTreeNodeStack; // Stack for TreeNode() when a NavLeft requested is emitted. + ImVectorTreeNodeStack; // Stack for TreeNode() // Viewports - ImVector Viewports; // Active viewports (Size==1 in 'master' branch). Each viewports hold their copy of ImDrawData. - - // Gamepad/keyboard Navigation - ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusedWindow' + ImVector Viewports; // Active viewports (always 1+, and generally 1 unless multi-viewports are enabled). Each viewports hold their copy of ImDrawData. + ImGuiViewportP* CurrentViewport; // We track changes of viewport (happening in Begin) so we can call Platform_OnChangedViewport() + ImGuiViewportP* MouseViewport; + ImGuiViewportP* MouseLastHoveredViewport; // Last known viewport that was hovered by mouse (even if we are not hovering any viewport any more) + honoring the _NoInputs flag. + ImGuiID PlatformLastFocusedViewportId; + ImGuiPlatformMonitor FallbackMonitor; // Virtual monitor used as fallback if backend doesn't provide monitor information. + ImRect PlatformMonitorsFullWorkRect; // Bounding box of all platform monitors + int ViewportCreatedCount; // Unique sequential creation counter (mostly for testing/debugging) + int PlatformWindowsCreatedCount; // Unique sequential creation counter (mostly for testing/debugging) + int ViewportFocusedStampCount; // Every time the front-most window changes, we stamp its viewport with an incrementing counter + + // Keyboard/Gamepad Navigation + bool NavCursorVisible; // Nav focus cursor/rectangle is visible? We hide it after a mouse click. We show it after a nav move. + bool NavHighlightItemUnderNav; // Disable mouse hovering highlight. Highlight navigation focused item instead of mouse hovered item. + //bool NavDisableHighlight; // Old name for !g.NavCursorVisible before 1.91.4 (2024/10/18). OPPOSITE VALUE (g.NavDisableHighlight == !g.NavCursorVisible) + //bool NavDisableMouseHover; // Old name for g.NavHighlightItemUnderNav before 1.91.1 (2024/10/18) this was called When user starts using keyboard/gamepad, we hide mouse hovering highlight until mouse is touched again. + bool NavMousePosDirty; // When set we will update mouse position if io.ConfigNavMoveSetMousePos is set (not enabled by default) + bool NavIdIsAlive; // Nav widget has been seen this frame ~~ NavRectRel is valid ImGuiID NavId; // Focused item for navigation + ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusedWindow' ImGuiID NavFocusScopeId; // Focused focus scope (e.g. selection code often wants to "clear other items" when landing on an item of the same scope) + ImGuiNavLayer NavLayer; // Focused layer (main scrolling layer, or menu/title bar layer) ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItem() ImGuiID NavActivateDownId; // ~~ IsKeyDown(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyDown(ImGuiKey_NavGamepadActivate) ? NavId : 0 ImGuiID NavActivatePressedId; // ~~ IsKeyPressed(ImGuiKey_Space) || IsKeyPressed(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate) ? NavId : 0 (no repeat) @@ -2049,18 +2373,11 @@ struct ImGuiContext ImVector NavFocusRoute; // Reversed copy focus scope stack for NavId (should contains NavFocusScopeId). This essentially follow the window->ParentWindowForFocusRoute chain. ImGuiID NavHighlightActivatedId; float NavHighlightActivatedTimer; - ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest). - ImGuiID NavJustMovedToFocusScopeId; // Just navigated to this focus scope id (result of a successfully MoveRequest). - ImGuiKeyChord NavJustMovedToKeyMods; ImGuiID NavNextActivateId; // Set by ActivateItem(), queued until next frame. ImGuiActivateFlags NavNextActivateFlags; ImGuiInputSource NavInputSource; // Keyboard or Gamepad mode? THIS CAN ONLY BE ImGuiInputSource_Keyboard or ImGuiInputSource_Mouse - ImGuiNavLayer NavLayer; // Layer we are navigating on. For now the system is hard-coded for 0=main contents and 1=menu/title bar, may expose layers later. ImGuiSelectionUserData NavLastValidSelectionUserData; // Last valid data passed to SetNextItemSelectionUser(), or -1. For current window. Not reset when focusing an item that doesn't have selection data. - bool NavIdIsAlive; // Nav widget has been seen this frame ~~ NavRectRel is valid - bool NavMousePosDirty; // When set we will update mouse position if (io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) if set (NB: this not enabled by default) - bool NavDisableHighlight; // When user starts using mouse, we hide gamepad/keyboard highlight (NB: but they are still available, which is why NavDisableHighlight isn't always != NavDisableMouseHover) - bool NavDisableMouseHover; // When user starts using gamepad/keyboard, we hide mouse hovering highlight until mouse is touched again. + ImS8 NavCursorHideFrames; // Navigation: Init & Move Requests bool NavAnyRequest; // ~~ NavMoveRequest || NavInitRequest this is to perform early out in ItemAdd() @@ -2086,6 +2403,14 @@ struct ImGuiContext ImGuiNavItemData NavMoveResultOther; // Best move request candidate within NavWindow's flattened hierarchy (when using ImGuiWindowFlags_NavFlattened flag) ImGuiNavItemData NavTabbingResultFirst; // First tabbing request candidate within NavWindow and flattened hierarchy + // Navigation: record of last move request + ImGuiID NavJustMovedFromFocusScopeId; // Just navigated from this focus scope id (result of a successfully MoveRequest). + ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest). + ImGuiID NavJustMovedToFocusScopeId; // Just navigated to this focus scope id (result of a successfully MoveRequest). + ImGuiKeyChord NavJustMovedToKeyMods; + bool NavJustMovedToIsTabbing; // Copy of ImGuiNavMoveFlags_IsTabbing. Maybe we should store whole flags. + bool NavJustMovedToHasSelectionData; // Copy of move result's ItemFlags & ImGuiItemFlags_HasSelectionUserData). Maybe we should just store ImGuiNavItemData. + // Navigation: Windowing (CTRL+TAB for list, or Menu button + keys or directional pads to move/resize) ImGuiKeyChord ConfigNavWindowingKeyNext; // = ImGuiMod_Ctrl | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiKey_Tab on OS X). For reconfiguration (see #4828) ImGuiKeyChord ConfigNavWindowingKeyPrev; // = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiMod_Shift | ImGuiKey_Tab on OS X) @@ -2141,6 +2466,13 @@ struct ImGuiContext ImVector CurrentTabBarStack; ImVector ShrinkWidthBuffer; + // Multi-Select state + ImGuiBoxSelectState BoxSelectState; + ImGuiMultiSelectTempData* CurrentMultiSelect; + int MultiSelectTempDataStacked; // Temporary multi-select data size (because we leave previous instances undestructed, we generally don't use MultiSelectTempData.Size) + ImVector MultiSelectTempData; + ImPool MultiSelectStorage; + // Hover Delay system ImGuiID HoverItemDelayId; ImGuiID HoverItemDelayIdPreviousFrame; @@ -2172,8 +2504,8 @@ struct ImGuiContext ImGuiComboPreviewData ComboPreviewData; ImRect WindowResizeBorderExpectedRect; // Expected border rect, switch to relative edit if moving bool WindowResizeRelativeMode; - short ScrollbarSeekMode; // 0: relative, -1/+1: prev/next page. - float ScrollbarClickDeltaToGrabCenter; // Distance between mouse and center of grab box, normalized in parent space. Use storage? + short ScrollbarSeekMode; // 0: scroll to clicked location, -1/+1: prev/next page. + float ScrollbarClickDeltaToGrabCenter; // When scrolling to mouse location: distance between mouse and center of grab box, normalized in parent space. float SliderGrabClickOffset; float SliderCurrentAccum; // Accumulated slider delta when using navigation controls. bool SliderCurrentAccumDirty; // Has the accumulated slider delta changed since last time we tried to apply it? @@ -2182,15 +2514,21 @@ struct ImGuiContext float DragSpeedDefaultRatio; // If speed == 0.0f, uses (max-min) * DragSpeedDefaultRatio float DisabledAlphaBackup; // Backup for style.Alpha for BeginDisabled() short DisabledStackSize; - short LockMarkEdited; short TooltipOverrideCount; + ImGuiWindow* TooltipPreviousWindow; // Window of last tooltip submitted during the frame ImVector ClipboardHandlerData; // If no custom clipboard handler is defined ImVector MenusIdSubmittedThisFrame; // A list of menu IDs that were rendered at least once ImGuiTypingSelectState TypingSelectState; // State for GetTypingSelectRequest() // Platform support ImGuiPlatformImeData PlatformImeData; // Data updated by current frame - ImGuiPlatformImeData PlatformImeDataPrev; // Previous frame data (when changing we will call io.SetPlatformImeDataFn + ImGuiPlatformImeData PlatformImeDataPrev; // Previous frame data. When changed we call the platform_io.Platform_SetImeDataFn() handler. + ImGuiID PlatformImeViewport; + + // Extensions + // FIXME: We could provide an API to register one slot in an array held in ImGuiContext? + ImGuiDockContext DockContext; + void (*DockNodeWindowMenuHandler)(ImGuiContext* ctx, ImGuiDockNode* node, ImGuiTabBar* tab_bar); // Settings bool SettingsLoaded; @@ -2207,7 +2545,8 @@ struct ImGuiContext // Capture/Logging bool LogEnabled; // Currently capturing - ImGuiLogType LogType; // Capture target + ImGuiLogFlags LogFlags; // Capture flags/type + ImGuiWindow* LogWindow; ImFileHandle LogFile; // If != NULL log to stdout/ file ImGuiTextBuffer LogBuffer; // Accumulation buffer when log to clipboard. This is pointer so our GImGui static constructor doesn't call heap allocators. const char* LogNextPrefix; @@ -2218,11 +2557,22 @@ struct ImGuiContext int LogDepthToExpand; int LogDepthToExpandDefault; // Default/stored value for LogDepthMaxExpand if not specified in the LogXXX function call. + // Error Handling + ImGuiErrorCallback ErrorCallback; // = NULL. May be exposed in public API eventually. + void* ErrorCallbackUserData; // = NULL + ImVec2 ErrorTooltipLockedPos; + bool ErrorFirst; + int ErrorCountCurrentFrame; // [Internal] Number of errors submitted this frame. + ImGuiErrorRecoveryState StackSizesInNewFrame; // [Internal] + ImGuiErrorRecoveryState*StackSizesInBeginForCurrentWindow; // [Internal] + // Debug Tools // (some of the highly frequently used data are interleaved in other structures above: DebugBreakXXX fields, DebugHookIdInfo, DebugLocateId etc.) + int DebugDrawIdConflictsCount; // Locked count (preserved when holding CTRL) ImGuiDebugLogFlags DebugLogFlags; ImGuiTextBuffer DebugLogBuf; ImGuiTextIndex DebugLogIndex; + int DebugLogSkippedErrors; ImGuiDebugLogFlags DebugLogAutoDisableFlags; ImU8 DebugLogAutoDisableFrames; ImU8 DebugLocateFrames; // For DebugLocateItemOnHover(). This is used together with DebugLocateId which is in a hot/cached spot above. @@ -2237,6 +2587,7 @@ struct ImGuiContext ImGuiMetricsConfig DebugMetricsConfig; ImGuiIDStackTool DebugIDStackTool; ImGuiDebugAllocInfo DebugAllocInfo; + ImGuiDockNode* DebugHoveredDockNode; // Hovered dock node. // Misc float FramerateSecPerFrame[60]; // Calculate estimate of framerate for user over the last 60 frames.. @@ -2249,202 +2600,7 @@ struct ImGuiContext ImVector TempBuffer; // Temporary text buffer char TempKeychordName[64]; - ImGuiContext(ImFontAtlas* shared_font_atlas) - { - IO.Ctx = this; - InputTextState.Ctx = this; - - Initialized = false; - FontAtlasOwnedByContext = shared_font_atlas ? false : true; - Font = NULL; - FontSize = FontBaseSize = CurrentDpiScale = 0.0f; - IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); - Time = 0.0f; - FrameCount = 0; - FrameCountEnded = FrameCountRendered = -1; - WithinFrameScope = WithinFrameScopeWithImplicitWindow = WithinEndChild = false; - GcCompactAll = false; - TestEngineHookItems = false; - TestEngine = NULL; - - InputEventsNextMouseSource = ImGuiMouseSource_Mouse; - InputEventsNextEventId = 1; - - WindowsActiveCount = 0; - CurrentWindow = NULL; - HoveredWindow = NULL; - HoveredWindowUnderMovingWindow = NULL; - MovingWindow = NULL; - WheelingWindow = NULL; - WheelingWindowStartFrame = WheelingWindowScrolledFrame = -1; - WheelingWindowReleaseTimer = 0.0f; - - DebugHookIdInfo = 0; - HoveredId = HoveredIdPreviousFrame = 0; - HoveredIdAllowOverlap = false; - HoveredIdIsDisabled = false; - HoveredIdTimer = HoveredIdNotActiveTimer = 0.0f; - ItemUnclipByLog = false; - ActiveId = 0; - ActiveIdIsAlive = 0; - ActiveIdTimer = 0.0f; - ActiveIdIsJustActivated = false; - ActiveIdAllowOverlap = false; - ActiveIdNoClearOnFocusLoss = false; - ActiveIdHasBeenPressedBefore = false; - ActiveIdHasBeenEditedBefore = false; - ActiveIdHasBeenEditedThisFrame = false; - ActiveIdFromShortcut = false; - ActiveIdClickOffset = ImVec2(-1, -1); - ActiveIdWindow = NULL; - ActiveIdSource = ImGuiInputSource_None; - ActiveIdMouseButton = -1; - ActiveIdPreviousFrame = 0; - ActiveIdPreviousFrameIsAlive = false; - ActiveIdPreviousFrameHasBeenEditedBefore = false; - ActiveIdPreviousFrameWindow = NULL; - LastActiveId = 0; - LastActiveIdTimer = 0.0f; - - LastKeyboardKeyPressTime = LastKeyModsChangeTime = LastKeyModsChangeFromNoneTime = -1.0; - - ActiveIdUsingNavDirMask = 0x00; - ActiveIdUsingAllKeyboardKeys = false; -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - ActiveIdUsingNavInputMask = 0x00; -#endif - - CurrentFocusScopeId = 0; - CurrentItemFlags = ImGuiItemFlags_None; - DebugShowGroupRects = false; - - NavWindow = NULL; - NavId = NavFocusScopeId = NavActivateId = NavActivateDownId = NavActivatePressedId = 0; - NavJustMovedToId = NavJustMovedToFocusScopeId = NavNextActivateId = 0; - NavActivateFlags = NavNextActivateFlags = ImGuiActivateFlags_None; - NavHighlightActivatedId = 0; - NavHighlightActivatedTimer = 0.0f; - NavJustMovedToKeyMods = ImGuiMod_None; - NavInputSource = ImGuiInputSource_Keyboard; - NavLayer = ImGuiNavLayer_Main; - NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid; - NavIdIsAlive = false; - NavMousePosDirty = false; - NavDisableHighlight = true; - NavDisableMouseHover = false; - NavAnyRequest = false; - NavInitRequest = false; - NavInitRequestFromMove = false; - NavMoveSubmitted = false; - NavMoveScoringItems = false; - NavMoveForwardToNextFrame = false; - NavMoveFlags = ImGuiNavMoveFlags_None; - NavMoveScrollFlags = ImGuiScrollFlags_None; - NavMoveKeyMods = ImGuiMod_None; - NavMoveDir = NavMoveDirForDebug = NavMoveClipDir = ImGuiDir_None; - NavScoringDebugCount = 0; - NavTabbingDir = 0; - NavTabbingCounter = 0; - - // All platforms use Ctrl+Tab but Ctrl<>Super are swapped on Mac... - // FIXME: Because this value is stored, it annoyingly interfere with toggling io.ConfigMacOSXBehaviors updating this.. - ConfigNavWindowingKeyNext = IO.ConfigMacOSXBehaviors ? (ImGuiMod_Super | ImGuiKey_Tab) : (ImGuiMod_Ctrl | ImGuiKey_Tab); - ConfigNavWindowingKeyPrev = IO.ConfigMacOSXBehaviors ? (ImGuiMod_Super | ImGuiMod_Shift | ImGuiKey_Tab) : (ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab); - NavWindowingTarget = NavWindowingTargetAnim = NavWindowingListWindow = NULL; - NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f; - NavWindowingToggleLayer = false; - NavWindowingToggleKey = ImGuiKey_None; - - DimBgRatio = 0.0f; - - DragDropActive = DragDropWithinSource = DragDropWithinTarget = false; - DragDropSourceFlags = ImGuiDragDropFlags_None; - DragDropSourceFrameCount = -1; - DragDropMouseButton = -1; - DragDropTargetId = 0; - DragDropAcceptFlags = ImGuiDragDropFlags_None; - DragDropAcceptIdCurrRectSurface = 0.0f; - DragDropAcceptIdPrev = DragDropAcceptIdCurr = 0; - DragDropAcceptFrameCount = -1; - DragDropHoldJustPressedId = 0; - memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal)); - - ClipperTempDataStacked = 0; - - CurrentTable = NULL; - TablesTempDataStacked = 0; - CurrentTabBar = NULL; - - HoverItemDelayId = HoverItemDelayIdPreviousFrame = HoverItemUnlockedStationaryId = HoverWindowUnlockedStationaryId = 0; - HoverItemDelayTimer = HoverItemDelayClearTimer = 0.0f; - - MouseCursor = ImGuiMouseCursor_Arrow; - MouseStationaryTimer = 0.0f; - - TempInputId = 0; - memset(&DataTypeZeroValue, 0, sizeof(DataTypeZeroValue)); - BeginMenuDepth = BeginComboDepth = 0; - ColorEditOptions = ImGuiColorEditFlags_DefaultOptions_; - ColorEditCurrentID = ColorEditSavedID = 0; - ColorEditSavedHue = ColorEditSavedSat = 0.0f; - ColorEditSavedColor = 0; - WindowResizeRelativeMode = false; - ScrollbarSeekMode = 0; - ScrollbarClickDeltaToGrabCenter = 0.0f; - SliderGrabClickOffset = 0.0f; - SliderCurrentAccum = 0.0f; - SliderCurrentAccumDirty = false; - DragCurrentAccumDirty = false; - DragCurrentAccum = 0.0f; - DragSpeedDefaultRatio = 1.0f / 100.0f; - DisabledAlphaBackup = 0.0f; - DisabledStackSize = 0; - LockMarkEdited = 0; - TooltipOverrideCount = 0; - - PlatformImeData.InputPos = ImVec2(0.0f, 0.0f); - PlatformImeDataPrev.InputPos = ImVec2(-1.0f, -1.0f); // Different to ensure initial submission - - SettingsLoaded = false; - SettingsDirtyTimer = 0.0f; - HookIdNext = 0; - - memset(LocalizationTable, 0, sizeof(LocalizationTable)); - - LogEnabled = false; - LogType = ImGuiLogType_None; - LogNextPrefix = LogNextSuffix = NULL; - LogFile = NULL; - LogLinePosY = FLT_MAX; - LogLineFirstItem = false; - LogDepthRef = 0; - LogDepthToExpand = LogDepthToExpandDefault = 2; - - DebugLogFlags = ImGuiDebugLogFlags_OutputToTTY; - DebugLocateId = 0; - DebugLogAutoDisableFlags = ImGuiDebugLogFlags_None; - DebugLogAutoDisableFrames = 0; - DebugLocateFrames = 0; - DebugBeginReturnValueCullDepth = -1; - DebugItemPickerActive = false; - DebugItemPickerMouseButton = ImGuiMouseButton_Left; - DebugItemPickerBreakId = 0; - DebugFlashStyleColorTime = 0.0f; - DebugFlashStyleColorIdx = ImGuiCol_COUNT; - - // Same as DebugBreakClearData(). Those fields are scattered in their respective subsystem to stay in hot-data locations - DebugBreakInWindow = 0; - DebugBreakInTable = 0; - DebugBreakInLocateId = false; - DebugBreakKeyChord = ImGuiKey_Pause; - DebugBreakInShortcutRouting = ImGuiKey_None; - - memset(FramerateSecPerFrame, 0, sizeof(FramerateSecPerFrame)); - FramerateSecPerFrameIdx = FramerateSecPerFrameCount = 0; - FramerateSecPerFrameAccum = 0.0f; - WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1; - memset(TempKeychordName, 0, sizeof(TempKeychordName)); - } + ImGuiContext(ImFontAtlas* shared_font_atlas); }; //----------------------------------------------------------------------------- @@ -2486,7 +2642,7 @@ struct IMGUI_API ImGuiWindowTempData ImVec2 MenuBarOffset; // MenuBarOffset.x is sort of equivalent of a per-layer CursorPos.x, saved/restored as we switch to the menu bar. The only situation when MenuBarOffset.y is > 0 if when (SafeAreaPadding.y > FramePadding.y), often used on TVs. ImGuiMenuColumns MenuColumns; // Simplified columns storage for menu items measurement int TreeDepth; // Current tree depth. - ImU32 TreeJumpToParentOnPopMask; // Store a copy of !g.NavIdIsAlive for TreeDepth 0..31.. Could be turned into a ImU64 if necessary. + ImU32 TreeHasStackDataDepthMask; // Store whether given depth has ImGuiTreeNodeStackData data. Could be turned into a ImU64 if necessary. ImVector ChildWindows; ImGuiStorage* StateStorage; // Current persistent per-window storage (store e.g. tree node open/close state) ImGuiOldColumns* CurrentColumns; // Current columns set @@ -2509,9 +2665,13 @@ struct IMGUI_API ImGuiWindow ImGuiContext* Ctx; // Parent UI context (needs to be set explicitly by parent). char* Name; // Window name, owned by the window. ImGuiID ID; // == ImHashStr(Name) - ImGuiWindowFlags Flags; // See enum ImGuiWindowFlags_ + ImGuiWindowFlags Flags, FlagsPreviousFrame; // See enum ImGuiWindowFlags_ ImGuiChildFlags ChildFlags; // Set when window is a child window. See enum ImGuiChildFlags_ + ImGuiWindowClass WindowClass; // Advanced users only. Set with SetNextWindowClass() ImGuiViewportP* Viewport; // Always set in Begin(). Inactive windows may have a NULL value here if their viewport was discarded. + ImGuiID ViewportId; // We backup the viewport id (since the viewport may disappear or never be created if the window is inactive) + ImVec2 ViewportPos; // We backup the viewport position (since the viewport may disappear or never be created if the window is inactive) + int ViewportAllowPlatformMonitorExtend; // Reset to -1 every frame (index is guaranteed to be valid between NewFrame..EndFrame), only used in the Appearing frame of a tooltip/popup to enforce clamping to a given monitor ImVec2 Pos; // Position (always rounded-up to nearest pixel) ImVec2 Size; // Current size (==SizeFull or collapsed title bar size) ImVec2 SizeFull; // Size when non collapsed @@ -2521,12 +2681,13 @@ struct IMGUI_API ImGuiWindow ImVec2 WindowPadding; // Window padding at the time of Begin(). float WindowRounding; // Window rounding at the time of Begin(). May be clamped lower to avoid rendering artifacts with title bar, menu bar etc. float WindowBorderSize; // Window border size at the time of Begin(). - float TitleBarHeight, MenuBarHeight; + float TitleBarHeight, MenuBarHeight; // Note that those used to be function before 2024/05/28. If you have old code calling TitleBarHeight() you can change it to TitleBarHeight. float DecoOuterSizeX1, DecoOuterSizeY1; // Left/Up offsets. Sum of non-scrolling outer decorations (X1 generally == 0.0f. Y1 generally = TitleBarHeight + MenuBarHeight). Locked during Begin(). float DecoOuterSizeX2, DecoOuterSizeY2; // Right/Down offsets (X2 generally == ScrollbarSize.x, Y2 == ScrollbarSizes.y). float DecoInnerSizeX1, DecoInnerSizeY1; // Applied AFTER/OVER InnerRect. Specialized for Tables as they use specialized form of clipping and frozen rows/columns are inside InnerRect (and not part of regular decoration sizes). int NameBufLen; // Size of buffer storing Name. May be larger than strlen(Name)! ImGuiID MoveId; // == window->GetID("#MOVE") + ImGuiID TabId; // == window->GetID("#TAB") ImGuiID ChildId; // ID of corresponding item in parent window (for navigation to return from child window to parent window) ImGuiID PopupId; // ID in the popup stack when this window is used as a popup/menu (because we use generic Name/ID for recycling) ImVec2 Scroll; @@ -2536,6 +2697,7 @@ struct IMGUI_API ImGuiWindow ImVec2 ScrollTargetEdgeSnapDist; // 0.0f = no snapping, >0.0f snapping threshold ImVec2 ScrollbarSizes; // Size taken by each scrollbars on their smaller axis. Pay attention! ScrollbarSizes.x == width of the vertical scrollbar, ScrollbarSizes.y = height of the horizontal scrollbar. bool ScrollbarX, ScrollbarY; // Are scrollbars visible? + bool ViewportOwned; bool Active; // Set to true on Begin(), unless Collapsed bool WasActive; bool WriteAccessed; // Set to true when any widget access the current window @@ -2565,6 +2727,7 @@ struct IMGUI_API ImGuiWindow ImGuiCond SetWindowPosAllowFlags : 8; // store acceptable condition flags for SetNextWindowPos() use. ImGuiCond SetWindowSizeAllowFlags : 8; // store acceptable condition flags for SetNextWindowSize() use. ImGuiCond SetWindowCollapsedAllowFlags : 8; // store acceptable condition flags for SetNextWindowCollapsed() use. + ImGuiCond SetWindowDockAllowFlags : 8; // store acceptable condition flags for SetNextWindowDock() use. ImVec2 SetWindowPosVal; // store window position when using a non-zero Pivot (position set needs to be processed when we know the window size) ImVec2 SetWindowPosPivot; // store window pivot for positioning. ImVec2(0, 0) when positioning from top-left corner; ImVec2(0.5f, 0.5f) for centering; ImVec2(1, 1) for bottom right. @@ -2584,11 +2747,13 @@ struct IMGUI_API ImGuiWindow ImVec2ih HitTestHoleOffset; int LastFrameActive; // Last frame number the window was Active. + int LastFrameJustFocused; // Last frame number the window was made Focused. float LastTimeActive; // Last timestamp the window was Active (using float as we don't need high precision there) float ItemWidthDefault; ImGuiStorage StateStorage; ImVector ColumnsStorage; float FontWindowScale; // User scale multiplier per-window, via SetWindowFontScale() + float FontDpiScale; int SettingsOffset; // Offset into SettingsWindows[] (offsets are always valid as we only grow the array from the back) ImDrawList* DrawList; // == &DrawListInst (for backward compatibility reason with code using imgui_internal.h we keep this a pointer) @@ -2597,6 +2762,7 @@ struct IMGUI_API ImGuiWindow ImGuiWindow* ParentWindowInBeginStack; ImGuiWindow* RootWindow; // Point to ourself or first ancestor that is not a child window. Doesn't cross through popups/dock nodes. ImGuiWindow* RootWindowPopupTree; // Point to ourself or first ancestor that is not a child window. Cross through popups parent<>child. + ImGuiWindow* RootWindowDockTree; // Point to ourself or first ancestor that is not a child window. Cross through dock nodes. ImGuiWindow* RootWindowForTitleBarHighlight; // Point to ourself or first ancestor which will display TitleBgActive color when this window is active. ImGuiWindow* RootWindowForNav; // Point to ourself or first ancestor which doesn't have the NavFlattened flag. ImGuiWindow* ParentWindowForFocusRoute; // Set to manual link a window to its logical parent so that Shortcut() chain are honoerd (e.g. Tool linked to Document) @@ -2611,6 +2777,19 @@ struct IMGUI_API ImGuiWindow int MemoryDrawListVtxCapacity; bool MemoryCompacted; // Set when window extraneous data have been garbage collected + // Docking + bool DockIsActive :1; // When docking artifacts are actually visible. When this is set, DockNode is guaranteed to be != NULL. ~~ (DockNode != NULL) && (DockNode->Windows.Size > 1). + bool DockNodeIsVisible :1; + bool DockTabIsVisible :1; // Is our window visible this frame? ~~ is the corresponding tab selected? + bool DockTabWantClose :1; + short DockOrder; // Order of the last time the window was visible within its DockNode. This is used to reorder windows that are reappearing on the same frame. Same value between windows that were active and windows that were none are possible. + ImGuiWindowDockStyle DockStyle; + ImGuiDockNode* DockNode; // Which node are we docked into. Important: Prefer testing DockIsActive in many cases as this will still be set when the dock node is hidden. + ImGuiDockNode* DockNodeAsHost; // Which node are we owning (for parent windows) + ImGuiID DockId; // Backup of last valid DockNode->ID, so single window remember their dock node id even when they are not bound any more + ImGuiItemStatusFlags DockTabItemStatusFlags; + ImRect DockTabItemRect; + public: ImGuiWindow(ImGuiContext* context, const char* name); ~ImGuiWindow(); @@ -2618,11 +2797,12 @@ struct IMGUI_API ImGuiWindow ImGuiID GetID(const char* str, const char* str_end = NULL); ImGuiID GetID(const void* ptr); ImGuiID GetID(int n); + ImGuiID GetIDFromPos(const ImVec2& p_abs); ImGuiID GetIDFromRectangle(const ImRect& r_abs); // We don't use g.FontSize because the window may be != g.CurrentWindow. ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } - float CalcFontSize() const { ImGuiContext& g = *Ctx; float scale = g.FontBaseSize * FontWindowScale; if (ParentWindow) scale *= ParentWindow->FontWindowScale; return scale; } + float CalcFontSize() const { ImGuiContext& g = *Ctx; float scale = g.FontBaseSize * FontWindowScale * FontDpiScale; if (ParentWindow) scale *= ParentWindow->FontWindowScale; return scale; } ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight)); } ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight; return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight); } }; @@ -2645,13 +2825,15 @@ enum ImGuiTabItemFlagsPrivate_ ImGuiTabItemFlags_SectionMask_ = ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing, ImGuiTabItemFlags_NoCloseButton = 1 << 20, // Track whether p_open was set or not (we'll need this info on the next frame to recompute ContentWidth during layout) ImGuiTabItemFlags_Button = 1 << 21, // Used by TabItemButton, change the tab item behavior to mimic a button + ImGuiTabItemFlags_Unsorted = 1 << 22, // [Docking] Trailing tabs with the _Unsorted flag will be sorted based on the DockOrder of their Window. }; -// Storage for one active tab item (sizeof() 40 bytes) +// Storage for one active tab item (sizeof() 48 bytes) struct ImGuiTabItem { ImGuiID ID; ImGuiTabItemFlags Flags; + ImGuiWindow* Window; // When TabItem is part of a DockNode's TabBar, we hold on to a window. int LastFrameVisible; int LastFrameSelected; // This allows us to infer an ordered list of the last activated tabs with little maintenance float Offset; // Position relative to beginning of tab @@ -2666,9 +2848,10 @@ struct ImGuiTabItem ImGuiTabItem() { memset(this, 0, sizeof(*this)); LastFrameVisible = LastFrameSelected = -1; RequestedWidth = -1.0f; NameOffset = -1; BeginOrder = IndexDuringLayout = -1; } }; -// Storage for a tab bar (sizeof() 152 bytes) +// Storage for a tab bar (sizeof() 160 bytes) struct IMGUI_API ImGuiTabBar { + ImGuiWindow* Window; ImVector Tabs; ImGuiTabBarFlags Flags; ImGuiID ID; // Zero for tab-bars used by docking @@ -2729,6 +2912,7 @@ struct ImGuiTableColumn float MaxX; float WidthRequest; // Master width absolute value when !(Flags & _WidthStretch). When Stretch this is derived every frame from StretchWeight in TableUpdateLayout() float WidthAuto; // Automatic width + float WidthMax; // Maximum width (FIXME: overwritten by each instance) float StretchWeight; // Master width weight when (Flags & _WidthStretch). Often around ~1.0f initially. float InitStretchWeightOrWidth; // Value passed to TableSetupColumn(). For Width it is a content width (_without padding_). ImRect ClipRect; // Clipping rectangle for the column @@ -3008,6 +3192,8 @@ namespace ImGui // If this ever crashes because g.CurrentWindow is NULL, it means that either: // - ImGui::NewFrame() has never been called, which is illegal. // - You are calling ImGui functions after ImGui::EndFrame()/ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal. + IMGUI_API ImGuiIO& GetIOEx(ImGuiContext* ctx); + IMGUI_API ImGuiPlatformIO& GetPlatformIOEx(ImGuiContext* ctx); inline ImGuiWindow* GetCurrentWindowRead() { ImGuiContext& g = *GImGui; return g.CurrentWindow; } inline ImGuiWindow* GetCurrentWindow() { ImGuiContext& g = *GImGui; g.CurrentWindow->WriteAccessed = true; return g.CurrentWindow; } IMGUI_API ImGuiWindow* FindWindowByID(ImGuiID id); @@ -3015,7 +3201,7 @@ namespace ImGui IMGUI_API void UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window); IMGUI_API void UpdateWindowSkipRefresh(ImGuiWindow* window); IMGUI_API ImVec2 CalcWindowNextAutoFitSize(ImGuiWindow* window); - IMGUI_API bool IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent, bool popup_hierarchy); + IMGUI_API bool IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent, bool popup_hierarchy, bool dock_hierarchy); IMGUI_API bool IsWindowWithinBeginStackOf(ImGuiWindow* window, ImGuiWindow* potential_parent); IMGUI_API bool IsWindowAbove(ImGuiWindow* potential_above, ImGuiWindow* potential_below); IMGUI_API bool IsWindowNavFocusable(ImGuiWindow* window); @@ -3024,9 +3210,10 @@ namespace ImGui IMGUI_API void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond = 0); IMGUI_API void SetWindowHitTestHole(ImGuiWindow* window, const ImVec2& pos, const ImVec2& size); IMGUI_API void SetWindowHiddenAndSkipItemsForCurrentFrame(ImGuiWindow* window); - inline void SetWindowParentWindowForFocusRoute(ImGuiWindow* window, ImGuiWindow* parent_window) { window->ParentWindowForFocusRoute = parent_window; } + inline void SetWindowParentWindowForFocusRoute(ImGuiWindow* window, ImGuiWindow* parent_window) { window->ParentWindowForFocusRoute = parent_window; } // You may also use SetNextWindowClass()'s FocusRouteParentWindowId field. inline ImRect WindowRectAbsToRel(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->DC.CursorStartPos; return ImRect(r.Min.x - off.x, r.Min.y - off.y, r.Max.x - off.x, r.Max.y - off.y); } inline ImRect WindowRectRelToAbs(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->DC.CursorStartPos; return ImRect(r.Min.x + off.x, r.Min.y + off.y, r.Max.x + off.x, r.Max.y + off.y); } + inline ImVec2 WindowPosAbsToRel(ImGuiWindow* window, const ImVec2& p) { ImVec2 off = window->DC.CursorStartPos; return ImVec2(p.x - off.x, p.y - off.y); } inline ImVec2 WindowPosRelToAbs(ImGuiWindow* window, const ImVec2& p) { ImVec2 off = window->DC.CursorStartPos; return ImVec2(p.x + off.x, p.y + off.y); } // Windows: Display Order and Focus Order @@ -3045,9 +3232,7 @@ namespace ImGui // Fonts, drawing IMGUI_API void SetCurrentFont(ImFont* font); inline ImFont* GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; } - inline ImDrawList* GetForegroundDrawList(ImGuiWindow* window) { IM_UNUSED(window); return GetForegroundDrawList(); } // This seemingly unnecessary wrapper simplifies compatibility between the 'master' and 'docking' branches. - IMGUI_API ImDrawList* GetBackgroundDrawList(ImGuiViewport* viewport); // get background draw list for the given viewport. this draw list will be the first rendering one. Useful to quickly draw shapes/text behind dear imgui contents. - IMGUI_API ImDrawList* GetForegroundDrawList(ImGuiViewport* viewport); // get foreground draw list for the given viewport. this draw list will be the last rendered one. Useful to quickly draw shapes/text over dear imgui contents. + inline ImDrawList* GetForegroundDrawList(ImGuiWindow* window) { return GetForegroundDrawList(window->Viewport); } IMGUI_API void AddDrawListToDrawDataEx(ImDrawData* draw_data, ImVector* out_list, ImDrawList* draw_list); // Init @@ -3059,6 +3244,7 @@ namespace ImGui IMGUI_API void UpdateHoveredWindowAndCaptureFlags(); IMGUI_API void FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_viewport, ImGuiWindow** out_hovered_window, ImGuiWindow** out_hovered_window_under_moving_window); IMGUI_API void StartMouseMovingWindow(ImGuiWindow* window); + IMGUI_API void StartMouseMovingWindowOrNode(ImGuiWindow* window, ImGuiDockNode* node, bool undock); IMGUI_API void UpdateMouseMovingWindowNewFrame(); IMGUI_API void UpdateMouseMovingWindowEndFrame(); @@ -3068,7 +3254,13 @@ namespace ImGui IMGUI_API void CallContextHooks(ImGuiContext* context, ImGuiContextHookType type); // Viewports + IMGUI_API void TranslateWindowsInViewport(ImGuiViewportP* viewport, const ImVec2& old_pos, const ImVec2& new_pos, const ImVec2& old_size, const ImVec2& new_size); + IMGUI_API void ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale); + IMGUI_API void DestroyPlatformWindow(ImGuiViewportP* viewport); IMGUI_API void SetWindowViewport(ImGuiWindow* window, ImGuiViewportP* viewport); + IMGUI_API void SetCurrentViewport(ImGuiWindow* window, ImGuiViewportP* viewport); + IMGUI_API const ImGuiPlatformMonitor* GetViewportPlatformMonitor(ImGuiViewport* viewport); + IMGUI_API ImGuiViewportP* FindHoveredViewportFromPlatformWindowStack(const ImVec2& mouse_platform_pos); // Settings IMGUI_API void MarkIniSettingsDirty(); @@ -3103,8 +3295,8 @@ namespace ImGui //#endif // Basic Accessors - inline ImGuiItemStatusFlags GetItemStatusFlags(){ ImGuiContext& g = *GImGui; return g.LastItemData.StatusFlags; } - inline ImGuiItemFlags GetItemFlags() { ImGuiContext& g = *GImGui; return g.LastItemData.InFlags; } + inline ImGuiItemStatusFlags GetItemStatusFlags() { ImGuiContext& g = *GImGui; return g.LastItemData.StatusFlags; } + inline ImGuiItemFlags GetItemFlags() { ImGuiContext& g = *GImGui; return g.LastItemData.ItemFlags; } inline ImGuiID GetActiveID() { ImGuiContext& g = *GImGui; return g.ActiveId; } inline ImGuiID GetFocusID() { ImGuiContext& g = *GImGui; return g.NavId; } IMGUI_API void SetActiveID(ImGuiID id, ImGuiWindow* window); @@ -3129,19 +3321,15 @@ namespace ImGui IMGUI_API ImVec2 CalcItemSize(ImVec2 size, float default_w, float default_h); IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x); IMGUI_API void PushMultiItemsWidths(int components, float width_full); - IMGUI_API bool IsItemToggledSelection(); // Was the last item selection toggled? (after Selectable(), TreeNode() etc. We only returns toggle _event_ in order to handle clipping correctly) - IMGUI_API ImVec2 GetContentRegionMaxAbs(); IMGUI_API void ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess); // Parameter stacks (shared) - IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); - IMGUI_API void PopItemFlag(); IMGUI_API const ImGuiDataVarInfo* GetStyleVarInfo(ImGuiStyleVar idx); IMGUI_API void BeginDisabledOverrideReenable(); IMGUI_API void EndDisabledOverrideReenable(); // Logging/Capture - IMGUI_API void LogBegin(ImGuiLogType type, int auto_open_depth); // -> BeginCapture() when we design v2 api, for now stay under the radar by using the old name. + IMGUI_API void LogBegin(ImGuiLogFlags flags, int auto_open_depth); // -> BeginCapture() when we design v2 api, for now stay under the radar by using the old name. IMGUI_API void LogToBuffer(int auto_open_depth = -1); // Start logging/capturing to internal buffer IMGUI_API void LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL); IMGUI_API void LogSetNextTextDecoration(const char* prefix, const char* suffix); @@ -3177,20 +3365,20 @@ namespace ImGui IMGUI_API bool BeginComboPreview(); IMGUI_API void EndComboPreview(); - // Gamepad/Keyboard Navigation + // Keyboard/Gamepad Navigation IMGUI_API void NavInitWindow(ImGuiWindow* window, bool force_reinit); IMGUI_API void NavInitRequestApplyResult(); IMGUI_API bool NavMoveRequestButNoResultYet(); IMGUI_API void NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags); IMGUI_API void NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags); IMGUI_API void NavMoveRequestResolveWithLastItem(ImGuiNavItemData* result); - IMGUI_API void NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiNavTreeNodeData* tree_node_data); + IMGUI_API void NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiTreeNodeStackData* tree_node_data); IMGUI_API void NavMoveRequestCancel(); IMGUI_API void NavMoveRequestApplyResult(); IMGUI_API void NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags); IMGUI_API void NavHighlightActivated(ImGuiID id); IMGUI_API void NavClearPreferredPosForAxis(ImGuiAxis axis); - IMGUI_API void NavRestoreHighlightAfterMove(); + IMGUI_API void SetNavCursorVisibleAfterMove(); IMGUI_API void NavUpdateCurrentWindowIsScrollPushableX(); IMGUI_API void SetNavWindow(ImGuiWindow* window); IMGUI_API void SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel); @@ -3211,7 +3399,7 @@ namespace ImGui inline bool IsGamepadKey(ImGuiKey key) { return key >= ImGuiKey_Gamepad_BEGIN && key < ImGuiKey_Gamepad_END; } inline bool IsMouseKey(ImGuiKey key) { return key >= ImGuiKey_Mouse_BEGIN && key < ImGuiKey_Mouse_END; } inline bool IsAliasKey(ImGuiKey key) { return key >= ImGuiKey_Aliases_BEGIN && key < ImGuiKey_Aliases_END; } - inline bool IsModKey(ImGuiKey key) { return key >= ImGuiKey_LeftCtrl && key <= ImGuiKey_RightSuper; } + inline bool IsLRModKey(ImGuiKey key) { return key >= ImGuiKey_LeftCtrl && key <= ImGuiKey_RightSuper; } ImGuiKeyChord FixupKeyChord(ImGuiKeyChord key_chord); inline ImGuiKey ConvertSingleModFlagToKey(ImGuiKey key) { @@ -3249,7 +3437,7 @@ namespace ImGui IMGUI_API ImGuiID GetKeyOwner(ImGuiKey key); IMGUI_API void SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags = 0); IMGUI_API void SetKeyOwnersForKeyChord(ImGuiKeyChord key, ImGuiID owner_id, ImGuiInputFlags flags = 0); - IMGUI_API void SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags = 0); // Set key owner to last item if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'. + IMGUI_API void SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags); // Set key owner to last item if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'. IMGUI_API bool TestKeyOwner(ImGuiKey key, ImGuiID owner_id); // Test that key is either not owned, either owned by 'owner_id' inline ImGuiKeyOwnerData* GetKeyOwnerData(ImGuiContext* ctx, ImGuiKey key) { if (key & ImGuiMod_Mask_) key = ConvertSingleModFlagToKey(key); IM_ASSERT(IsNamedKey(key)); return &ctx->KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN]; } @@ -3287,6 +3475,61 @@ namespace ImGui IMGUI_API bool TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id); IMGUI_API ImGuiKeyRoutingData* GetShortcutRoutingData(ImGuiKeyChord key_chord); + // Docking + // (some functions are only declared in imgui.cpp, see Docking section) + IMGUI_API void DockContextInitialize(ImGuiContext* ctx); + IMGUI_API void DockContextShutdown(ImGuiContext* ctx); + IMGUI_API void DockContextClearNodes(ImGuiContext* ctx, ImGuiID root_id, bool clear_settings_refs); // Use root_id==0 to clear all + IMGUI_API void DockContextRebuildNodes(ImGuiContext* ctx); + IMGUI_API void DockContextNewFrameUpdateUndocking(ImGuiContext* ctx); + IMGUI_API void DockContextNewFrameUpdateDocking(ImGuiContext* ctx); + IMGUI_API void DockContextEndFrame(ImGuiContext* ctx); + IMGUI_API ImGuiID DockContextGenNodeID(ImGuiContext* ctx); + IMGUI_API void DockContextQueueDock(ImGuiContext* ctx, ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload, ImGuiDir split_dir, float split_ratio, bool split_outer); + IMGUI_API void DockContextQueueUndockWindow(ImGuiContext* ctx, ImGuiWindow* window); + IMGUI_API void DockContextQueueUndockNode(ImGuiContext* ctx, ImGuiDockNode* node); + IMGUI_API void DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* window, bool clear_persistent_docking_ref = true); + IMGUI_API void DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node); + IMGUI_API bool DockContextCalcDropPosForDocking(ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload_window, ImGuiDockNode* payload_node, ImGuiDir split_dir, bool split_outer, ImVec2* out_pos); + IMGUI_API ImGuiDockNode*DockContextFindNodeByID(ImGuiContext* ctx, ImGuiID id); + IMGUI_API void DockNodeWindowMenuHandler_Default(ImGuiContext* ctx, ImGuiDockNode* node, ImGuiTabBar* tab_bar); + IMGUI_API bool DockNodeBeginAmendTabBar(ImGuiDockNode* node); + IMGUI_API void DockNodeEndAmendTabBar(); + inline ImGuiDockNode* DockNodeGetRootNode(ImGuiDockNode* node) { while (node->ParentNode) node = node->ParentNode; return node; } + inline bool DockNodeIsInHierarchyOf(ImGuiDockNode* node, ImGuiDockNode* parent) { while (node) { if (node == parent) return true; node = node->ParentNode; } return false; } + inline int DockNodeGetDepth(const ImGuiDockNode* node) { int depth = 0; while (node->ParentNode) { node = node->ParentNode; depth++; } return depth; } + inline ImGuiID DockNodeGetWindowMenuButtonId(const ImGuiDockNode* node) { return ImHashStr("#COLLAPSE", 0, node->ID); } + inline ImGuiDockNode* GetWindowDockNode() { ImGuiContext& g = *GImGui; return g.CurrentWindow->DockNode; } + IMGUI_API bool GetWindowAlwaysWantOwnTabBar(ImGuiWindow* window); + IMGUI_API void BeginDocked(ImGuiWindow* window, bool* p_open); + IMGUI_API void BeginDockableDragDropSource(ImGuiWindow* window); + IMGUI_API void BeginDockableDragDropTarget(ImGuiWindow* window); + IMGUI_API void SetWindowDock(ImGuiWindow* window, ImGuiID dock_id, ImGuiCond cond); + + // Docking - Builder function needs to be generally called before the node is used/submitted. + // - The DockBuilderXXX functions are designed to _eventually_ become a public API, but it is too early to expose it and guarantee stability. + // - Do not hold on ImGuiDockNode* pointers! They may be invalidated by any split/merge/remove operation and every frame. + // - To create a DockSpace() node, make sure to set the ImGuiDockNodeFlags_DockSpace flag when calling DockBuilderAddNode(). + // You can create dockspace nodes (attached to a window) _or_ floating nodes (carry its own window) with this API. + // - DockBuilderSplitNode() create 2 child nodes within 1 node. The initial node becomes a parent node. + // - If you intend to split the node immediately after creation using DockBuilderSplitNode(), make sure + // to call DockBuilderSetNodeSize() beforehand. If you don't, the resulting split sizes may not be reliable. + // - Call DockBuilderFinish() after you are done. + IMGUI_API void DockBuilderDockWindow(const char* window_name, ImGuiID node_id); + IMGUI_API ImGuiDockNode*DockBuilderGetNode(ImGuiID node_id); + inline ImGuiDockNode* DockBuilderGetCentralNode(ImGuiID node_id) { ImGuiDockNode* node = DockBuilderGetNode(node_id); if (!node) return NULL; return DockNodeGetRootNode(node)->CentralNode; } + IMGUI_API ImGuiID DockBuilderAddNode(ImGuiID node_id = 0, ImGuiDockNodeFlags flags = 0); + IMGUI_API void DockBuilderRemoveNode(ImGuiID node_id); // Remove node and all its child, undock all windows + IMGUI_API void DockBuilderRemoveNodeDockedWindows(ImGuiID node_id, bool clear_settings_refs = true); + IMGUI_API void DockBuilderRemoveNodeChildNodes(ImGuiID node_id); // Remove all split/hierarchy. All remaining docked windows will be re-docked to the remaining root node (node_id). + IMGUI_API void DockBuilderSetNodePos(ImGuiID node_id, ImVec2 pos); + IMGUI_API void DockBuilderSetNodeSize(ImGuiID node_id, ImVec2 size); + IMGUI_API ImGuiID DockBuilderSplitNode(ImGuiID node_id, ImGuiDir split_dir, float size_ratio_for_node_at_dir, ImGuiID* out_id_at_dir, ImGuiID* out_id_at_opposite_dir); // Create 2 child nodes in this parent node. + IMGUI_API void DockBuilderCopyDockSpace(ImGuiID src_dockspace_id, ImGuiID dst_dockspace_id, ImVector* in_window_remap_pairs); + IMGUI_API void DockBuilderCopyNode(ImGuiID src_node_id, ImGuiID dst_node_id, ImVector* out_node_remap_pairs); + IMGUI_API void DockBuilderCopyWindowSettings(const char* src_name, const char* dst_name); + IMGUI_API void DockBuilderFinish(ImGuiID node_id); + // [EXPERIMENTAL] Focus Scope // This is generally used to identify a unique input location (for e.g. a selection set) // There is one per window (automatically set in Begin), but: @@ -3307,11 +3550,25 @@ namespace ImGui IMGUI_API void RenderDragDropTargetRect(const ImRect& bb, const ImRect& item_clip_rect); // Typing-Select API + // (provide Windows Explorer style "select items by typing partial name" + "cycle through items by typing same letter" feature) + // (this is currently not documented nor used by main library, but should work. See "widgets_typingselect" in imgui_test_suite for usage code. Please let us know if you use this!) IMGUI_API ImGuiTypingSelectRequest* GetTypingSelectRequest(ImGuiTypingSelectFlags flags = ImGuiTypingSelectFlags_None); IMGUI_API int TypingSelectFindMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx); IMGUI_API int TypingSelectFindNextSingleCharMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx); IMGUI_API int TypingSelectFindBestLeadingMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data); + // Box-Select API + IMGUI_API bool BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiID box_select_id, ImGuiMultiSelectFlags ms_flags); + IMGUI_API void EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flags); + + // Multi-Select API + IMGUI_API void MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags* p_button_flags); + IMGUI_API void MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed); + IMGUI_API void MultiSelectAddSetAll(ImGuiMultiSelectTempData* ms, bool selected); + IMGUI_API void MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item); + inline ImGuiBoxSelectState* GetBoxSelectState(ImGuiID id) { ImGuiContext& g = *GImGui; return (id != 0 && g.BoxSelectState.ID == id && g.BoxSelectState.IsActive) ? &g.BoxSelectState : NULL; } + inline ImGuiMultiSelectState* GetMultiSelectState(ImGuiID id) { ImGuiContext& g = *GImGui; return g.MultiSelectStorage.GetByKey(id); } + // Internal Columns API (this is not exposed because we will encourage transitioning to the Tables API) IMGUI_API void SetWindowClipRectBeforeSetChannel(ImGuiWindow* window, const ImRect& clip_rect); IMGUI_API void BeginColumns(const char* str_id, int count, ImGuiOldColumnFlags flags = 0); // setup number of columns. use an identifier to distinguish multiple column sets. close with EndColumns(). @@ -3328,7 +3585,6 @@ namespace ImGui IMGUI_API void TableOpenContextMenu(int column_n = -1); IMGUI_API void TableSetColumnWidth(int column_n, float width); IMGUI_API void TableSetColumnSortDirection(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs); - IMGUI_API int TableGetHoveredColumn(); // May use (TableGetColumnFlags() & ImGuiTableColumnFlags_IsHovered) instead. Return hovered column. return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. IMGUI_API int TableGetHoveredRow(); // Retrieve *PREVIOUS FRAME* hovered row. This difference with TableGetHoveredColumn() is the reason why this is not public yet. IMGUI_API float TableGetHeaderRowHeight(); IMGUI_API float TableGetHeaderAngledMaxLabelWidth(); @@ -3364,7 +3620,7 @@ namespace ImGui IMGUI_API ImRect TableGetCellBgRect(const ImGuiTable* table, int column_n); IMGUI_API const char* TableGetColumnName(const ImGuiTable* table, int column_n); IMGUI_API ImGuiID TableGetColumnResizeID(ImGuiTable* table, int column_n, int instance_no = 0); - IMGUI_API float TableGetMaxColumnWidth(const ImGuiTable* table, int column_n); + IMGUI_API float TableCalcMaxColumnWidth(const ImGuiTable* table, int column_n); IMGUI_API void TableSetColumnWidthAutoSingle(ImGuiTable* table, int column_n); IMGUI_API void TableSetColumnWidthAutoAll(ImGuiTable* table); IMGUI_API void TableRemove(ImGuiTable* table); @@ -3386,12 +3642,15 @@ namespace ImGui IMGUI_API bool BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& bb, ImGuiTabBarFlags flags); IMGUI_API ImGuiTabItem* TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id); IMGUI_API ImGuiTabItem* TabBarFindTabByOrder(ImGuiTabBar* tab_bar, int order); + IMGUI_API ImGuiTabItem* TabBarFindMostRecentlySelectedTabForActiveWindow(ImGuiTabBar* tab_bar); IMGUI_API ImGuiTabItem* TabBarGetCurrentTab(ImGuiTabBar* tab_bar); inline int TabBarGetTabOrder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) { return tab_bar->Tabs.index_from_ptr(tab); } IMGUI_API const char* TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); + IMGUI_API void TabBarAddTab(ImGuiTabBar* tab_bar, ImGuiTabItemFlags tab_flags, ImGuiWindow* window); IMGUI_API void TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id); IMGUI_API void TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); IMGUI_API void TabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); + IMGUI_API void TabBarQueueFocus(ImGuiTabBar* tab_bar, const char* tab_name); IMGUI_API void TabBarQueueReorder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, int offset); IMGUI_API void TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, ImVec2 mouse_pos); IMGUI_API bool TabBarProcessReorder(ImGuiTabBar* tab_bar); @@ -3409,10 +3668,13 @@ namespace ImGui IMGUI_API void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL); IMGUI_API void RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL); IMGUI_API void RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float clip_max_x, float ellipsis_max_x, const char* text, const char* text_end, const ImVec2* text_size_if_known); - IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f); + IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool borders = true, float rounding = 0.0f); IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, ImDrawFlags flags = 0); - IMGUI_API void RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags = ImGuiNavHighlightFlags_None); // Navigation highlight + IMGUI_API void RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags = ImGuiNavRenderCursorFlags_None); // Navigation highlight +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + inline void RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags = ImGuiNavRenderCursorFlags_None) { RenderNavCursor(bb, id, flags); } // Renamed in 1.91.4 +#endif IMGUI_API const char* FindRenderedTextEnd(const char* text, const char* text_end = NULL); // Find the optional ## from which we stop displaying text. IMGUI_API void RenderMouseCursor(ImVec2 pos, float scale, ImGuiMouseCursor mouse_cursor, ImU32 col_fill, ImU32 col_border, ImU32 col_shadow); @@ -3421,14 +3683,16 @@ namespace ImGui IMGUI_API void RenderBullet(ImDrawList* draw_list, ImVec2 pos, ImU32 col); IMGUI_API void RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz); IMGUI_API void RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col); + IMGUI_API void RenderArrowDockMenu(ImDrawList* draw_list, ImVec2 p_min, float sz, ImU32 col); IMGUI_API void RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding); IMGUI_API void RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const ImRect& inner, ImU32 col, float rounding); + IMGUI_API ImDrawFlags CalcRoundingFlagsForRectInRect(const ImRect& r_in, const ImRect& r_outer, float threshold); // Widgets IMGUI_API void TextEx(const char* text, const char* text_end = NULL, ImGuiTextFlags flags = 0); IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0, 0), ImGuiButtonFlags flags = 0); IMGUI_API bool ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags = 0); - IMGUI_API bool ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags = 0); + IMGUI_API bool ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags = 0); IMGUI_API void SeparatorEx(ImGuiSeparatorFlags flags, float thickness = 1.0f); IMGUI_API void SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_width); IMGUI_API bool CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value); @@ -3436,9 +3700,9 @@ namespace ImGui // Widgets: Window Decorations IMGUI_API bool CloseButton(ImGuiID id, const ImVec2& pos); - IMGUI_API bool CollapseButton(ImGuiID id, const ImVec2& pos); + IMGUI_API bool CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_node); IMGUI_API void Scrollbar(ImGuiAxis axis); - IMGUI_API bool ScrollbarEx(const ImRect& bb, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 avail_v, ImS64 contents_v, ImDrawFlags flags); + IMGUI_API bool ScrollbarEx(const ImRect& bb, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 avail_v, ImS64 contents_v, ImDrawFlags draw_rounding_flags = 0); IMGUI_API ImRect GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis); IMGUI_API ImGuiID GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis); IMGUI_API ImGuiID GetWindowResizeCornerID(ImGuiWindow* window, int n); // 0..3: corners @@ -3449,11 +3713,13 @@ namespace ImGui IMGUI_API bool DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags); IMGUI_API bool SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb); IMGUI_API bool SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f, float hover_visibility_delay = 0.0f, ImU32 bg_col = 0); + + // Widgets: Tree Nodes IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL); IMGUI_API void TreePushOverrideID(ImGuiID id); - IMGUI_API void TreeNodeSetOpen(ImGuiID id, bool open); - IMGUI_API bool TreeNodeUpdateNextOpen(ImGuiID id, ImGuiTreeNodeFlags flags); // Return open state. Consume previous SetNextItemOpen() data, if any. May return true when logging. - IMGUI_API void SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data); + IMGUI_API bool TreeNodeGetOpen(ImGuiID storage_id); + IMGUI_API void TreeNodeSetOpen(ImGuiID storage_id, bool open); + IMGUI_API bool TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags); // Return open state. Consume previous SetNextItemOpen() data, if any. May return true when logging. // Template functions are instantiated in imgui_widgets.cpp for a finite number of types. // To use them externally (for custom widget) you may need an "extern template" statement in your code in order to link to existing instances and silence Clang warnings (see #2036). @@ -3472,6 +3738,7 @@ namespace ImGui IMGUI_API bool DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void* p_data, const char* format, void* p_data_when_empty = NULL); IMGUI_API int DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2); IMGUI_API bool DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max); + IMGUI_API bool DataTypeIsZero(ImGuiDataType data_type, const void* p_data); // InputText IMGUI_API bool InputTextEx(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); @@ -3500,15 +3767,18 @@ namespace ImGui IMGUI_API void GcCompactTransientWindowBuffers(ImGuiWindow* window); IMGUI_API void GcAwakeTransientWindowBuffers(ImGuiWindow* window); - // Debug Log - IMGUI_API void DebugLog(const char* fmt, ...) IM_FMTARGS(1); - IMGUI_API void DebugLogV(const char* fmt, va_list args) IM_FMTLIST(1); - IMGUI_API void DebugAllocHook(ImGuiDebugAllocInfo* info, int frame_count, void* ptr, size_t size); // size >= 0 : alloc, size = -1 : free + // Error handling, State Recovery + IMGUI_API bool ErrorLog(const char* msg); + IMGUI_API void ErrorRecoveryStoreState(ImGuiErrorRecoveryState* state_out); + IMGUI_API void ErrorRecoveryTryToRecoverState(const ImGuiErrorRecoveryState* state_in); + IMGUI_API void ErrorRecoveryTryToRecoverWindowState(const ImGuiErrorRecoveryState* state_in); + IMGUI_API void ErrorCheckUsingSetCursorPosToExtendParentBoundaries(); + IMGUI_API void ErrorCheckEndFrameFinalizeErrorTooltip(); + IMGUI_API bool BeginErrorTooltip(); + IMGUI_API void EndErrorTooltip(); // Debug Tools - IMGUI_API void ErrorCheckEndFrameRecover(ImGuiErrorLogCallback log_callback, void* user_data = NULL); - IMGUI_API void ErrorCheckEndWindowRecover(ImGuiErrorLogCallback log_callback, void* user_data = NULL); - IMGUI_API void ErrorCheckUsingSetCursorPosToExtendParentBoundaries(); + IMGUI_API void DebugAllocHook(ImGuiDebugAllocInfo* info, int frame_count, void* ptr, size_t size); // size >= 0 : alloc, size = -1 : free IMGUI_API void DebugDrawCursorPos(ImU32 col = IM_COL32(255, 0, 0, 255)); IMGUI_API void DebugDrawLineExtents(ImU32 col = IM_COL32(255, 0, 0, 255)); IMGUI_API void DebugDrawItemRect(ImU32 col = IM_COL32(255, 0, 0, 255)); @@ -3522,6 +3792,7 @@ namespace ImGui IMGUI_API void ShowFontAtlas(ImFontAtlas* atlas); IMGUI_API void DebugHookIdInfo(ImGuiID id, ImGuiDataType data_type, const void* data_id, const void* data_id_end); IMGUI_API void DebugNodeColumns(ImGuiOldColumns* columns); + IMGUI_API void DebugNodeDockNode(ImGuiDockNode* node, const char* label); IMGUI_API void DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, const ImDrawList* draw_list, const char* label); IMGUI_API void DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb); IMGUI_API void DebugNodeFont(ImFont* font); @@ -3532,19 +3803,20 @@ namespace ImGui IMGUI_API void DebugNodeTableSettings(ImGuiTableSettings* settings); IMGUI_API void DebugNodeInputTextState(ImGuiInputTextState* state); IMGUI_API void DebugNodeTypingSelectState(ImGuiTypingSelectState* state); + IMGUI_API void DebugNodeMultiSelectState(ImGuiMultiSelectState* state); IMGUI_API void DebugNodeWindow(ImGuiWindow* window, const char* label); IMGUI_API void DebugNodeWindowSettings(ImGuiWindowSettings* settings); IMGUI_API void DebugNodeWindowsList(ImVector* windows, const char* label); IMGUI_API void DebugNodeWindowsListByBeginStackParent(ImGuiWindow** windows, int windows_size, ImGuiWindow* parent_in_begin_stack); IMGUI_API void DebugNodeViewport(ImGuiViewportP* viewport); + IMGUI_API void DebugNodePlatformMonitor(ImGuiPlatformMonitor* monitor, const char* label, int idx); IMGUI_API void DebugRenderKeyboardPreview(ImDrawList* draw_list); IMGUI_API void DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* viewport, const ImRect& bb); // Obsolete functions #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - inline void SetItemUsingMouseWheel() { SetItemKeyOwner(ImGuiKey_MouseWheelY); } // Changed in 1.89 - inline bool TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0) { return TreeNodeUpdateNextOpen(id, flags); } // Renamed in 1.89 - + //inline void SetItemUsingMouseWheel() { SetItemKeyOwner(ImGuiKey_MouseWheelY); } // Changed in 1.89 + //inline bool TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0) { return TreeNodeUpdateNextOpen(id, flags); } // Renamed in 1.89 //inline bool IsKeyPressedMap(ImGuiKey key, bool repeat = true) { IM_ASSERT(IsNamedKey(key)); return IsKeyPressed(key, repeat); } // Removed in 1.87: Mapping from named key is always identity! // Refactored focus/nav/tabbing system in 1.82 and 1.84. If you have old/custom copy-and-pasted widgets which used FocusableItemRegister(): @@ -3562,7 +3834,8 @@ namespace ImGui // [SECTION] ImFontAtlas internal API //----------------------------------------------------------------------------- -// This structure is likely to evolve as we add support for incremental atlas updates +// This structure is likely to evolve as we add support for incremental atlas updates. +// Conceptually this could be in ImGuiPlatformIO, but we are far from ready to make this public. struct ImFontBuilderIO { bool (*FontBuilder_Build)(ImFontAtlas* atlas); @@ -3595,7 +3868,7 @@ extern const char* ImGuiTestEngine_FindItemDebugLabel(ImGuiContext* ctx, ImGuiI // In IMGUI_VERSION_NUM >= 18934: changed IMGUI_TEST_ENGINE_ITEM_ADD(bb,id) to IMGUI_TEST_ENGINE_ITEM_ADD(id,bb,item_data); #define IMGUI_TEST_ENGINE_ITEM_ADD(_ID,_BB,_ITEM_DATA) if (g.TestEngineHookItems) ImGuiTestEngineHook_ItemAdd(&g, _ID, _BB, _ITEM_DATA) // Register item bounding box #define IMGUI_TEST_ENGINE_ITEM_INFO(_ID,_LABEL,_FLAGS) if (g.TestEngineHookItems) ImGuiTestEngineHook_ItemInfo(&g, _ID, _LABEL, _FLAGS) // Register item label and status flags (optional) -#define IMGUI_TEST_ENGINE_LOG(_FMT,...) if (g.TestEngineHookItems) ImGuiTestEngineHook_Log(&g, _FMT, __VA_ARGS__) // Custom log entry from user land into test log +#define IMGUI_TEST_ENGINE_LOG(_FMT,...) ImGuiTestEngineHook_Log(&g, _FMT, __VA_ARGS__) // Custom log entry from user land into test log #else #define IMGUI_TEST_ENGINE_ITEM_ADD(_BB,_ID) ((void)0) #define IMGUI_TEST_ENGINE_ITEM_INFO(_ID,_LABEL,_FLAGS) ((void)g) diff --git a/ext/imgui/imgui_tables.cpp b/ext/imgui/imgui_tables.cpp index 475a717d06c9..f4e76225a828 100644 --- a/ext/imgui/imgui_tables.cpp +++ b/ext/imgui/imgui_tables.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.9 WIP +// dear imgui, v1.91.6 // (tables and columns code) /* @@ -187,8 +187,6 @@ Index of this file: // [SECTION] Header mess //----------------------------------------------------------------------------- -#undef new - #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif @@ -231,6 +229,7 @@ Index of this file: #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type #elif defined(__GNUC__) #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked @@ -330,7 +329,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // - always performing the GetOrAddByKey() O(log N) query in g.Tables.Map[]. const bool use_child_window = (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0; const ImVec2 avail_size = GetContentRegionAvail(); - const ImVec2 actual_outer_size = CalcItemSize(outer_size, ImMax(avail_size.x, 1.0f), use_child_window ? ImMax(avail_size.y, 1.0f) : 0.0f); + const ImVec2 actual_outer_size = ImTrunc(CalcItemSize(outer_size, ImMax(avail_size.x, 1.0f), use_child_window ? ImMax(avail_size.y, 1.0f) : 0.0f)); const ImRect outer_rect(outer_window->DC.CursorPos, outer_window->DC.CursorPos + actual_outer_size); const bool outer_window_is_measuring_size = (outer_window->AutoFitFramesX > 0) || (outer_window->AutoFitFramesY > 0); // Doesn't apply to AlwaysAutoResize windows! if (use_child_window && IsClippedEx(outer_rect, 0) && !outer_window_is_measuring_size) @@ -411,11 +410,12 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // Reset scroll if we are reactivating it if ((previous_flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) == 0) - SetNextWindowScroll(ImVec2(0.0f, 0.0f)); + if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasScroll) == 0) + SetNextWindowScroll(ImVec2(0.0f, 0.0f)); // Create scrolling region (without border and zero window padding) - ImGuiWindowFlags child_flags = (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None; - BeginChildEx(name, instance_id, outer_rect.GetSize(), false, child_flags); + ImGuiWindowFlags child_window_flags = (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None; + BeginChildEx(name, instance_id, outer_rect.GetSize(), ImGuiChildFlags_None, child_window_flags); table->InnerWindow = g.CurrentWindow; table->WorkRect = table->InnerWindow->WorkRect; table->OuterRect = table->InnerWindow->Rect(); @@ -462,16 +462,27 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG temp_data->HostBackupItemWidthStackSize = outer_window->DC.ItemWidthStack.Size; inner_window->DC.PrevLineSize = inner_window->DC.CurrLineSize = ImVec2(0.0f, 0.0f); - // Make left and top borders not overlap our contents by offsetting HostClipRect (#6765) + // Make borders not overlap our contents by offsetting HostClipRect (#6765, #7428, #3752) // (we normally shouldn't alter HostClipRect as we rely on TableMergeDrawChannels() expanding non-clipped column toward the // limits of that rectangle, in order for ImDrawListSplitter::Merge() to merge the draw commands. However since the overlap // problem only affect scrolling tables in this case we can get away with doing it without extra cost). if (inner_window != outer_window) { + // FIXME: Because inner_window's Scrollbar doesn't know about border size, since it's not encoded in window->WindowBorderSize, + // it already overlaps it and doesn't need an extra offset. Ideally we should be able to pass custom border size with + // different x/y values to BeginChild(). if (flags & ImGuiTableFlags_BordersOuterV) + { table->HostClipRect.Min.x = ImMin(table->HostClipRect.Min.x + TABLE_BORDER_SIZE, table->HostClipRect.Max.x); + if (inner_window->DecoOuterSizeX2 == 0.0f) + table->HostClipRect.Max.x = ImMax(table->HostClipRect.Max.x - TABLE_BORDER_SIZE, table->HostClipRect.Min.x); + } if (flags & ImGuiTableFlags_BordersOuterH) + { table->HostClipRect.Min.y = ImMin(table->HostClipRect.Min.y + TABLE_BORDER_SIZE, table->HostClipRect.Max.y); + if (inner_window->DecoOuterSizeY2 == 0.0f) + table->HostClipRect.Max.y = ImMax(table->HostClipRect.Max.y - TABLE_BORDER_SIZE, table->HostClipRect.Min.y); + } } // Padding and Spacing @@ -499,7 +510,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->InnerClipRect = (inner_window == outer_window) ? table->WorkRect : inner_window->ClipRect; table->InnerClipRect.ClipWith(table->WorkRect); // We need this to honor inner_width table->InnerClipRect.ClipWithFull(table->HostClipRect); - table->InnerClipRect.Max.y = (flags & ImGuiTableFlags_NoHostExtendY) ? ImMin(table->InnerClipRect.Max.y, inner_window->WorkRect.Max.y) : inner_window->ClipRect.Max.y; + table->InnerClipRect.Max.y = (flags & ImGuiTableFlags_NoHostExtendY) ? ImMin(table->InnerClipRect.Max.y, inner_window->WorkRect.Max.y) : table->HostClipRect.Max.y; table->RowPosY1 = table->RowPosY2 = table->WorkRect.Min.y; // This is needed somehow table->RowTextBaseline = 0.0f; // This will be cleared again by TableBeginRow() @@ -857,7 +868,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Calculate ideal/auto column width (that's the width required for all contents to be visible without clipping) // Combine width from regular rows + width from headers unless requested not to. - if (!column->IsPreserveWidthAuto) + if (!column->IsPreserveWidthAuto && table->InstanceCurrent == 0) column->WidthAuto = TableGetColumnWidthAuto(table, column); // Non-resizable columns keep their requested width (apply user value regardless of IsPreserveWidthAuto) @@ -1061,16 +1072,12 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) continue; } - // Detect hovered column - if (is_hovering_table && mouse_skewed_x >= column->ClipRect.Min.x && mouse_skewed_x < column->ClipRect.Max.x) - table->HoveredColumnBody = (ImGuiTableColumnIdx)column_n; - // Lock start position column->MinX = offset_x; // Lock width based on start position and minimum/maximum width for this position - float max_width = TableGetMaxColumnWidth(table, column_n); - column->WidthGiven = ImMin(column->WidthGiven, max_width); + column->WidthMax = TableCalcMaxColumnWidth(table, column_n); + column->WidthGiven = ImMin(column->WidthGiven, column->WidthMax); column->WidthGiven = ImMax(column->WidthGiven, ImMin(column->WidthRequest, table->MinColumnWidth)); column->MaxX = offset_x + column->WidthGiven + table->CellSpacingX1 + table->CellSpacingX2 + table->CellPaddingX * 2.0f; @@ -1119,8 +1126,13 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->Flags |= ImGuiTableColumnFlags_IsVisible; if (column->SortOrder != -1) column->Flags |= ImGuiTableColumnFlags_IsSorted; - if (table->HoveredColumnBody == column_n) + + // Detect hovered column + if (is_hovering_table && mouse_skewed_x >= column->ClipRect.Min.x && mouse_skewed_x < column->ClipRect.Max.x) + { column->Flags |= ImGuiTableColumnFlags_IsHovered; + table->HoveredColumnBody = (ImGuiTableColumnIdx)column_n; + } // Alignment // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in @@ -1150,7 +1162,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) } // Don't decrement auto-fit counters until container window got a chance to submit its items - if (table->HostSkipItems == false) + if (table->HostSkipItems == false && table->InstanceCurrent == 0) { column->AutoFitQueue >>= 1; column->CannotSkipItemsQueue >>= 1; @@ -1251,7 +1263,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (table->Flags & ImGuiTableFlags_NoClip) table->DrawSplitter->SetCurrentChannel(inner_window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP); else - inner_window->DrawList->PushClipRect(inner_window->ClipRect.Min, inner_window->ClipRect.Max, false); + inner_window->DrawList->PushClipRect(inner_window->InnerClipRect.Min, inner_window->InnerClipRect.Max, false); // FIXME: use table->InnerClipRect? } // Process hit-testing on resizing borders. Actual size change will be applied in EndTable() @@ -1474,7 +1486,9 @@ void ImGui::EndTable() { short backup_nav_layers_active_mask = inner_window->DC.NavLayersActiveMask; inner_window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main; // So empty table don't appear to navigate differently. + g.CurrentTable = NULL; // To avoid error recovery recursing EndChild(); + g.CurrentTable = table; inner_window->DC.NavLayersActiveMask = backup_nav_layers_active_mask; } else @@ -1998,34 +2012,37 @@ void ImGui::TableEndRow(ImGuiTable* table) // We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark end of row and // get the new cursor position. if (unfreeze_rows_request) + { for (int column_n = 0; column_n < table->ColumnsCount; column_n++) table->Columns[column_n].NavLayerCurrent = ImGuiNavLayer_Main; - if (unfreeze_rows_actual) - { - IM_ASSERT(table->IsUnfrozenRows == false); - const float y0 = ImMax(table->RowPosY2 + 1, window->InnerClipRect.Min.y); - table->IsUnfrozenRows = true; + const float y0 = ImMax(table->RowPosY2 + 1, table->InnerClipRect.Min.y); table_instance->LastFrozenHeight = y0 - table->OuterRect.Min.y; - // BgClipRect starts as table->InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect - table->BgClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y = ImMin(y0, window->InnerClipRect.Max.y); - table->BgClipRect.Max.y = table->Bg2ClipRectForDrawCmd.Max.y = window->InnerClipRect.Max.y; - table->Bg2DrawChannelCurrent = table->Bg2DrawChannelUnfrozen; - IM_ASSERT(table->Bg2ClipRectForDrawCmd.Min.y <= table->Bg2ClipRectForDrawCmd.Max.y); - - float row_height = table->RowPosY2 - table->RowPosY1; - table->RowPosY2 = window->DC.CursorPos.y = table->WorkRect.Min.y + table->RowPosY2 - table->OuterRect.Min.y; - table->RowPosY1 = table->RowPosY2 - row_height; - for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + if (unfreeze_rows_actual) { - ImGuiTableColumn* column = &table->Columns[column_n]; - column->DrawChannelCurrent = column->DrawChannelUnfrozen; - column->ClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y; - } + IM_ASSERT(table->IsUnfrozenRows == false); + table->IsUnfrozenRows = true; + + // BgClipRect starts as table->InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect + table->BgClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y = ImMin(y0, table->InnerClipRect.Max.y); + table->BgClipRect.Max.y = table->Bg2ClipRectForDrawCmd.Max.y = table->InnerClipRect.Max.y; + table->Bg2DrawChannelCurrent = table->Bg2DrawChannelUnfrozen; + IM_ASSERT(table->Bg2ClipRectForDrawCmd.Min.y <= table->Bg2ClipRectForDrawCmd.Max.y); + + float row_height = table->RowPosY2 - table->RowPosY1; + table->RowPosY2 = window->DC.CursorPos.y = table->WorkRect.Min.y + table->RowPosY2 - table->OuterRect.Min.y; + table->RowPosY1 = table->RowPosY2 - row_height; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + column->DrawChannelCurrent = column->DrawChannelUnfrozen; + column->ClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y; + } - // Update cliprect ahead of TableBeginCell() so clipper can access to new ClipRect->Min.y - SetWindowClipRectBeforeSetChannel(window, table->Columns[0].ClipRect); - table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Columns[0].DrawChannelCurrent); + // Update cliprect ahead of TableBeginCell() so clipper can access to new ClipRect->Min.y + SetWindowClipRectBeforeSetChannel(window, table->Columns[0].ClipRect); + table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Columns[0].DrawChannelCurrent); + } } if (!(table->RowFlags & ImGuiTableRowFlags_Headers)) @@ -2194,8 +2211,8 @@ void ImGui::TableEndCell(ImGuiTable* table) // Note that actual columns widths are computed in TableUpdateLayout(). //------------------------------------------------------------------------- -// Maximum column content width given current layout. Use column->MinX so this value on a per-column basis. -float ImGui::TableGetMaxColumnWidth(const ImGuiTable* table, int column_n) +// Maximum column content width given current layout. Use column->MinX so this value differs on a per-column basis. +float ImGui::TableCalcMaxColumnWidth(const ImGuiTable* table, int column_n) { const ImGuiTableColumn* column = &table->Columns[column_n]; float max_width = FLT_MAX; @@ -2257,7 +2274,7 @@ void ImGui::TableSetColumnWidth(int column_n, float width) // Compare both requested and actual given width to avoid overwriting requested width when column is stuck (minimum size, bounded) IM_ASSERT(table->MinColumnWidth > 0.0f); const float min_width = table->MinColumnWidth; - const float max_width = ImMax(min_width, TableGetMaxColumnWidth(table, column_n)); + const float max_width = ImMax(min_width, column_0->WidthMax); // Don't use TableCalcMaxColumnWidth() here as it would rely on MinX from last instance (#7933) column_0_width = ImClamp(column_0_width, min_width, max_width); if (column_0->WidthGiven == column_0_width || column_0->WidthRequest == column_0_width) return; @@ -2742,7 +2759,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) const ImU32 outer_col = table->BorderColorStrong; if ((table->Flags & ImGuiTableFlags_BordersOuter) == ImGuiTableFlags_BordersOuter) { - inner_drawlist->AddRect(outer_border.Min, outer_border.Max + ImVec2(1, 1), outer_col, 0.0f, 0, border_size); + inner_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, 0, border_size); } else if (table->Flags & ImGuiTableFlags_BordersOuterV) { @@ -3006,7 +3023,8 @@ float ImGui::TableGetHeaderAngledMaxLabelWidth() // The intent is that advanced users willing to create customized headers would not need to use this helper // and can create their own! For example: TableHeader() may be preceded by Checkbox() or other custom widgets. // See 'Demo->Tables->Custom headers' for a demonstration of implementing a custom version of this. -// This code is constructed to not make much use of internal functions, as it is intended to be a template to copy. +// This code is intentionally written to not make much use of internal functions, to give you better direction +// if you need to write your own. // FIXME-TABLE: TableOpenContextMenu() and TableGetHeaderRowHeight() are not public. void ImGui::TableHeadersRow() { @@ -3014,7 +3032,8 @@ void ImGui::TableHeadersRow() ImGuiTable* table = g.CurrentTable; IM_ASSERT(table != NULL && "Need to call TableHeadersRow() after BeginTable()!"); - // Layout if not already done (this is automatically done by TableNextRow, we do it here solely to facilitate stepping in debugger as it is frequent to step in TableUpdateLayout) + // Call layout if not already done. This is automatically done by TableNextRow: we do it here _only_ to make + // it easier to debug-step in TableUpdateLayout(). Your own version of this function doesn't need this. if (!table->IsLayoutLocked) TableUpdateLayout(table); @@ -3031,8 +3050,7 @@ void ImGui::TableHeadersRow() if (!TableSetColumnIndex(column_n)) continue; - // Push an id to allow unnamed labels (generally accidental, but let's behave nicely with them) - // In your own code you may omit the PushID/PopID all-together, provided you know they won't collide. + // Push an id to allow empty/unnamed headers. This is also idiomatic as it ensure there is a consistent ID path to access columns (for e.g. automation) const char* name = (TableGetColumnFlags(column_n) & ImGuiTableColumnFlags_NoHeaderLabel) ? "" : TableGetColumnName(column_n); PushID(column_n); TableHeader(name); @@ -3123,7 +3141,7 @@ void ImGui::TableHeader(const char* label) if ((table->RowFlags & ImGuiTableRowFlags_Headers) == 0) TableSetBgColor(ImGuiTableBgTarget_CellBg, GetColorU32(ImGuiCol_TableHeaderBg), table->CurrentColumn); } - RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_Compact | ImGuiNavHighlightFlags_NoRounding); + RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact | ImGuiNavRenderCursorFlags_NoRounding); if (held) table->HeldHeaderColumn = (ImGuiTableColumnIdx)column_n; window->DC.CursorPos.y -= g.Style.ItemSpacing.y * 0.5f; @@ -3271,7 +3289,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label ButtonBehavior(row_r, row_id, NULL, NULL); KeepAliveID(row_id); - const float ascent_scaled = g.Font->Ascent * (g.FontSize / g.Font->FontSize); // FIXME: Standardize those scaling factors better + const float ascent_scaled = g.Font->Ascent * g.FontScale; // FIXME: Standardize those scaling factors better const float line_off_for_ascent_x = (ImMax((g.FontSize - ascent_scaled) * 0.5f, 0.0f) / -sin_a) * (flip_label ? -1.0f : 1.0f); const ImVec2 padding = g.Style.CellPadding; // We will always use swapped component const ImVec2 align = g.Style.TableAngledHeadersTextAlign; @@ -3473,7 +3491,7 @@ void ImGui::TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags Separator(); want_separator = true; - PushItemFlag(ImGuiItemFlags_SelectableDontClosePopup, true); + PushItemFlag(ImGuiItemFlags_AutoClosePopups, false); for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) { ImGuiTableColumn* other_column = &table->Columns[other_column_n]; @@ -4395,7 +4413,7 @@ void ImGui::EndColumns() { ButtonBehavior(column_hit_rect, column_id, &hovered, &held); if (hovered || held) - g.MouseCursor = ImGuiMouseCursor_ResizeEW; + SetMouseCursor(ImGuiMouseCursor_ResizeEW); if (held && !(column->Flags & ImGuiOldColumnFlags_NoResize)) dragging_column = n; } @@ -4427,12 +4445,12 @@ void ImGui::EndColumns() NavUpdateCurrentWindowIsScrollPushableX(); } -void ImGui::Columns(int columns_count, const char* id, bool border) +void ImGui::Columns(int columns_count, const char* id, bool borders) { ImGuiWindow* window = GetCurrentWindow(); IM_ASSERT(columns_count >= 1); - ImGuiOldColumnFlags flags = (border ? 0 : ImGuiOldColumnFlags_NoBorder); + ImGuiOldColumnFlags flags = (borders ? 0 : ImGuiOldColumnFlags_NoBorder); //flags |= ImGuiOldColumnFlags_NoPreserveWidths; // NB: Legacy behavior ImGuiOldColumns* columns = window->DC.CurrentColumns; if (columns != NULL && columns->Count == columns_count && columns->Flags == flags) diff --git a/ext/imgui/imgui_widgets.cpp b/ext/imgui/imgui_widgets.cpp index e3b969b2c727..d39a949d1f82 100644 --- a/ext/imgui/imgui_widgets.cpp +++ b/ext/imgui/imgui_widgets.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.9 WIP +// dear imgui, v1.91.6 // (widgets code) /* @@ -19,7 +19,9 @@ Index of this file: // [SECTION] Widgets: TreeNode, CollapsingHeader, etc. // [SECTION] Widgets: Selectable // [SECTION] Widgets: Typing-Select support +// [SECTION] Widgets: Box-Select support // [SECTION] Widgets: Multi-Select support +// [SECTION] Widgets: Multi-Select helpers // [SECTION] Widgets: ListBox // [SECTION] Widgets: PlotLines, PlotHistogram // [SECTION] Widgets: Value helpers @@ -30,8 +32,6 @@ Index of this file: */ -#undef new - #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif @@ -72,12 +72,14 @@ Index of this file: #pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. #pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. #pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness +#pragma clang diagnostic ignored "-Wunused-macros" // warning: macro is not used // we define snprintf/vsnprintf on Windows so they are available, but not always used. #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 #pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. #pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type #elif defined(__GNUC__) #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked @@ -127,7 +129,7 @@ static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1); // For InputTextEx() static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false); static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); -static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); +static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); //------------------------------------------------------------------------- // [SECTION] Widgets: Text, etc. @@ -424,6 +426,7 @@ void ImGui::BulletTextV(const char* fmt, va_list args) // - RadioButton() // - ProgressBar() // - Bullet() +// - Hyperlink() //------------------------------------------------------------------------- // The ButtonBehavior() function is key to many interactions and used by many/most widgets. @@ -480,32 +483,35 @@ void ImGui::BulletTextV(const char* fmt, va_list args) // Frame N + RepeatDelay + RepeatRate*N true true - true //------------------------------------------------------------------------------------------------------------------------------------------------- -// FIXME: For refactor we could output flags, incl mouse hovered vs nav keyboard vs nav triggered etc. -// And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);' -// For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading. +// - FIXME: For refactor we could output flags, incl mouse hovered vs nav keyboard vs nav triggered etc. +// And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);' +// For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading. +// - Since v1.91.2 (Sept 2024) we included io.ConfigDebugHighlightIdConflicts feature. +// One idiom which was previously valid which will now emit a warning is when using multiple overlayed ButtonBehavior() +// with same ID and different MouseButton (see #8030). You can fix it by: +// (1) switching to use a single ButtonBehavior() with multiple _MouseButton flags. +// or (2) surrounding those calls with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag() bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); + // Default behavior inherited from item flags + // Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that. + ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.ItemFlags : g.CurrentItemFlags); + if (flags & ImGuiButtonFlags_AllowOverlap) + item_flags |= ImGuiItemFlags_AllowOverlap; + // Default only reacts to left mouse button if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0) flags |= ImGuiButtonFlags_MouseButtonLeft; // Default behavior requires click + release inside bounding box if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0) - flags |= ImGuiButtonFlags_PressedOnDefault_; - - // Default behavior inherited from item flags - // Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that. - ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.InFlags : g.CurrentItemFlags); - if (flags & ImGuiButtonFlags_AllowOverlap) - item_flags |= ImGuiItemFlags_AllowOverlap; - if (flags & ImGuiButtonFlags_Repeat) - item_flags |= ImGuiItemFlags_ButtonRepeat; + flags |= (item_flags & ImGuiItemFlags_ButtonRepeat) ? ImGuiButtonFlags_PressedOnClick : ImGuiButtonFlags_PressedOnDefault_; ImGuiWindow* backup_hovered_window = g.HoveredWindow; - const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindow == window; + const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindowDockTree == window->RootWindowDockTree; if (flatten_hovered_children) g.HoveredWindow = window; @@ -554,7 +560,8 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool } // Process initial action - if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) + const bool mods_ok = !(flags & ImGuiButtonFlags_NoKeyModsAllowed) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt); + if (mods_ok) { if (mouse_button_clicked != -1 && g.ActiveId != id) { @@ -565,8 +572,14 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool SetActiveID(id, window); g.ActiveIdMouseButton = mouse_button_clicked; if (!(flags & ImGuiButtonFlags_NoNavFocus)) + { SetFocusID(id, window); - FocusWindow(window); + FocusWindow(window); + } + else + { + FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child + } } if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseClickedCount[mouse_button_clicked] == 2)) { @@ -575,10 +588,16 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool ClearActiveID(); else SetActiveID(id, window); // Hold on ID + g.ActiveIdMouseButton = mouse_button_clicked; if (!(flags & ImGuiButtonFlags_NoNavFocus)) + { SetFocusID(id, window); - g.ActiveIdMouseButton = mouse_button_clicked; - FocusWindow(window); + FocusWindow(window); + } + else + { + FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child + } } } if (flags & ImGuiButtonFlags_PressedOnRelease) @@ -589,7 +608,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool if (!has_repeated_at_least_once) pressed = true; if (!(flags & ImGuiButtonFlags_NoNavFocus)) - SetFocusID(id, window); + SetFocusID(id, window); // FIXME: Lack of FocusWindow() call here is inconsistent with other paths. Research why. ClearActiveID(); } } @@ -601,13 +620,13 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool pressed = true; } - if (pressed) - g.NavDisableHighlight = true; + if (pressed && g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = false; } - // Gamepad/Keyboard handling + // Keyboard/Gamepad navigation handling // We report navigated and navigation-activated items as hovered but we don't set g.HoveredId to not interfere with mouse. - if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover) + if (g.NavId == id && g.NavCursorVisible && g.NavHighlightItemUnderNav) if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus)) hovered = true; if (g.NavActivateDownId == id) @@ -670,8 +689,8 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool } ClearActiveID(); } - if (!(flags & ImGuiButtonFlags_NoNavFocus)) - g.NavDisableHighlight = true; + if (!(flags & ImGuiButtonFlags_NoNavFocus) && g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = false; } else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) { @@ -721,7 +740,7 @@ bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags // Render const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); - RenderNavHighlight(bb, id); + RenderNavCursor(bb, id); RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); if (g.LogEnabled) @@ -768,11 +787,12 @@ bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiBut ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f); const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); ItemSize(size); - if (!ItemAdd(bb, id)) + if (!ItemAdd(bb, id, NULL, (flags & ImGuiButtonFlags_EnableNav) ? ImGuiItemFlags_None : ImGuiItemFlags_NoNav)) return false; bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + RenderNavCursor(bb, id); IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags); return pressed; @@ -798,7 +818,7 @@ bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiBu // Render const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); const ImU32 text_col = GetColorU32(ImGuiCol_Text); - RenderNavHighlight(bb, id); + RenderNavCursor(bb, id); RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding); RenderArrow(window->DrawList, bb.Min + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), text_col, dir); @@ -836,22 +856,21 @@ bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) return pressed; // Render - // FIXME: Clarify this mess - ImU32 col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered); - ImVec2 center = bb.GetCenter(); + ImU32 bg_col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered); if (hovered) - window->DrawList->AddCircleFilled(center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f), col); - - float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; + window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); + RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); ImU32 cross_col = GetColorU32(ImGuiCol_Text); - center -= ImVec2(0.5f, 0.5f); - window->DrawList->AddLine(center + ImVec2(+cross_extent, +cross_extent), center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f); - window->DrawList->AddLine(center + ImVec2(+cross_extent, -cross_extent), center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f); + ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f); + float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; + window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f); + window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f); return pressed; } -bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos) +// The Collapse button also functions as a Dock Menu button. +bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_node) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; @@ -864,15 +883,21 @@ bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos) return pressed; // Render + //bool is_dock_menu = (window->DockNodeAsHost && !window->Collapsed); ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); ImU32 text_col = GetColorU32(ImGuiCol_Text); if (hovered || held) - window->DrawList->AddCircleFilled(bb.GetCenter() + ImVec2(0.0f, -0.5f), g.FontSize * 0.5f + 1.0f, bg_col); - RenderArrow(window->DrawList, bb.Min, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f); + window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); + RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); + + if (dock_node) + RenderArrowDockMenu(window->DrawList, bb.Min, g.FontSize, text_col); + else + RenderArrow(window->DrawList, bb.Min, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f); // Switch to moving the window after mouse is moved beyond the initial drag threshold if (IsItemActive() && IsMouseDragging(0)) - StartMouseMovingWindow(window); + StartMouseMovingWindowOrNode(window, dock_node, true); // Undock from window/collapse menu button return pressed; } @@ -931,7 +956,7 @@ void ImGui::Scrollbar(ImGuiAxis axis) // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. // Still, the code should probably be made simpler.. -bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_visible_v, ImS64 size_contents_v, ImDrawFlags flags) +bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_visible_v, ImS64 size_contents_v, ImDrawFlags draw_rounding_flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; @@ -986,9 +1011,10 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 const int held_dir = (clicked_v_norm < grab_v_norm) ? -1 : (clicked_v_norm > grab_v_norm + grab_h_norm) ? +1 : 0; if (g.ActiveIdIsJustActivated) { - // On initial click calculate the distance between mouse and the center of the grab - g.ScrollbarSeekMode = (short)held_dir; - g.ScrollbarClickDeltaToGrabCenter = (g.ScrollbarSeekMode == 0.0f) ? clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f : 0.0f; + // On initial click when held_dir == 0 (clicked over grab): calculate the distance between mouse and the center of the grab + const bool scroll_to_clicked_location = (g.IO.ConfigScrollbarScrollByPage == false || g.IO.KeyShift || held_dir == 0); + g.ScrollbarSeekMode = scroll_to_clicked_location ? 0 : (short)held_dir; + g.ScrollbarClickDeltaToGrabCenter = (held_dir == 0 && !g.IO.KeyShift) ? clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f : 0.0f; } // Apply scroll (p_scroll_v will generally point on one member of window->Scroll) @@ -1021,7 +1047,7 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 // Render const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg); const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha); - window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, flags); + window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, draw_rounding_flags); ImRect grab_rect; if (axis == ImGuiAxis_X) grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, bb.Max.y); @@ -1053,9 +1079,7 @@ void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const I window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); } -// ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390) -// We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API. -bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) +bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); @@ -1073,16 +1097,17 @@ bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& imag // Render const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); - RenderNavHighlight(bb, id); + RenderNavCursor(bb, id); RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding)); if (bg_col.w > 0.0f) window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col)); - window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); + window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); return pressed; } -// Note that ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button. +// - ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button. +// - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. (#8165) // FIXME: Maybe that's not the best design? bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) { ImGuiContext& g = *GImGui; @@ -1095,28 +1120,24 @@ bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const I #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Legacy API obsoleted in 1.89. Two differences with new ImageButton() -// - new ImageButton() requires an explicit 'const char* str_id' Old ImageButton() used opaque imTextureId (created issue with: multiple buttons with same image, transient texture id values, opaque computation of ID) -// - new ImageButton() always use style.FramePadding Old ImageButton() had an override argument. -// If you need to change padding with new ImageButton() you can use PushStyleVar(ImGuiStyleVar_FramePadding, value), consistent with other Button functions. +// - old ImageButton() used ImTextureID as item id (created issue with multiple buttons with same image, transient texture id values, opaque computation of ID) +// - new ImageButton() requires an explicit 'const char* str_id' +// - old ImageButton() had frame_padding' override argument. +// - new ImageButton() always use style.FramePadding. +/* bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col) { - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - if (window->SkipItems) - return false; - // Default to using texture ID as ID. User can still push string/integer prefixes. - PushID((void*)(intptr_t)user_texture_id); - const ImGuiID id = window->GetID("#image"); - PopID(); - + PushID((ImTextureID)(intptr_t)user_texture_id); if (frame_padding >= 0) PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2((float)frame_padding, (float)frame_padding)); - bool ret = ImageButtonEx(id, user_texture_id, size, uv0, uv1, bg_col, tint_col); + bool ret = ImageButton("", user_texture_id, size, uv0, uv1, bg_col, tint_col); if (frame_padding >= 0) PopStyleVar(); + PopID(); return ret; } +*/ #endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS bool ImGui::Checkbox(const char* label, bool* v) @@ -1134,42 +1155,60 @@ bool ImGui::Checkbox(const char* label, bool* v) const ImVec2 pos = window->DC.CursorPos; const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f)); ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, id)) - { - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); - return false; - } + const bool is_visible = ItemAdd(total_bb, id); + const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0; + if (!is_visible) + if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(total_bb)) // Extra layer of "no logic clip" for box-select support + { + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); + return false; + } + + // Range-Selection/Multi-selection support (header) + bool checked = *v; + if (is_multi_select) + MultiSelectItemHeader(id, &checked, NULL); bool hovered, held; bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); - if (pressed) + + // Range-Selection/Multi-selection support (footer) + if (is_multi_select) + MultiSelectItemFooter(id, &checked, &pressed); + else if (pressed) + checked = !checked; + + if (*v != checked) { - *v = !(*v); + *v = checked; + pressed = true; // return value MarkItemEdited(id); } const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); - RenderNavHighlight(total_bb, id); - RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); - ImU32 check_col = GetColorU32(ImGuiCol_CheckMark); - bool mixed_value = (g.LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0; - if (mixed_value) - { - // Undocumented tristate/mixed/indeterminate checkbox (#2644) - // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox) - ImVec2 pad(ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)), ImMax(1.0f, IM_TRUNC(square_sz / 3.6f))); - window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding); - } - else if (*v) - { - const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f)); - RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f); + const bool mixed_value = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; + if (is_visible) + { + RenderNavCursor(total_bb, id); + RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); + ImU32 check_col = GetColorU32(ImGuiCol_CheckMark); + if (mixed_value) + { + // Undocumented tristate/mixed/indeterminate checkbox (#2644) + // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox) + ImVec2 pad(ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)), ImMax(1.0f, IM_TRUNC(square_sz / 3.6f))); + window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding); + } + else if (*v) + { + const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f)); + RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f); + } } - - ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); + const ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); if (g.LogEnabled) LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]"); - if (label_size.x > 0.0f) + if (is_visible && label_size.x > 0.0f) RenderText(label_pos, label); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); @@ -1252,7 +1291,7 @@ bool ImGui::RadioButton(const char* label, bool active) if (pressed) MarkItemEdited(id); - RenderNavHighlight(total_bb, id); + RenderNavCursor(total_bb, id); const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius); window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segment); if (active) @@ -1369,6 +1408,79 @@ void ImGui::Bullet() SameLine(0, style.FramePadding.x * 2.0f); } +// This is provided as a convenience for being an often requested feature. +// FIXME-STYLE: we delayed adding as there is a larger plan to revamp the styling system. +// Because of this we currently don't provide many styling options for this widget +// (e.g. hovered/active colors are automatically inferred from a single color). +bool ImGui::TextLink(const char* label) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiID id = window->GetID(label); + const char* label_end = FindRenderedTextEnd(label); + + ImVec2 pos = window->DC.CursorPos; + ImVec2 size = CalcTextSize(label, label_end, true); + ImRect bb(pos, pos + size); + ItemSize(size, 0.0f); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + RenderNavCursor(bb, id); + + if (hovered) + SetMouseCursor(ImGuiMouseCursor_Hand); + + ImVec4 text_colf = g.Style.Colors[ImGuiCol_TextLink]; + ImVec4 line_colf = text_colf; + { + // FIXME-STYLE: Read comments above. This widget is NOT written in the same style as some earlier widgets, + // as we are currently experimenting/planning a different styling system. + float h, s, v; + ColorConvertRGBtoHSV(text_colf.x, text_colf.y, text_colf.z, h, s, v); + if (held || hovered) + { + v = ImSaturate(v + (held ? 0.4f : 0.3f)); + h = ImFmod(h + 0.02f, 1.0f); + } + ColorConvertHSVtoRGB(h, s, v, text_colf.x, text_colf.y, text_colf.z); + v = ImSaturate(v - 0.20f); + ColorConvertHSVtoRGB(h, s, v, line_colf.x, line_colf.y, line_colf.z); + } + + float line_y = bb.Max.y + ImFloor(g.Font->Descent * g.FontScale * 0.20f); + window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode. + + PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf)); + RenderText(bb.Min, label, label_end); + PopStyleColor(); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); + return pressed; +} + +void ImGui::TextLinkOpenURL(const char* label, const char* url) +{ + ImGuiContext& g = *GImGui; + if (url == NULL) + url = label; + if (TextLink(label)) + if (g.PlatformIO.Platform_OpenInShellFn != NULL) + g.PlatformIO.Platform_OpenInShellFn(&g, url); + SetItemTooltip(LocalizeGetMsg(ImGuiLocKey_OpenLink_s), url); // It is more reassuring for user to _always_ display URL when we same as label + if (BeginPopupContextItem()) + { + if (MenuItem(LocalizeGetMsg(ImGuiLocKey_CopyLink))) + SetClipboardText(url); + EndPopup(); + } +} + //------------------------------------------------------------------------- // [SECTION] Widgets: Low-level Layout helpers //------------------------------------------------------------------------- @@ -1527,7 +1639,7 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end const float separator_thickness = style.SeparatorTextBorderSize; const ImVec2 min_size(label_size.x + extra_w + padding.x * 2.0f, ImMax(label_size.y + padding.y * 2.0f, separator_thickness)); const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y)); - const float text_baseline_y = ImTrunc((bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.99999f); //ImMax(padding.y, ImFloor((style.SeparatorTextSize - label_size.y) * 0.5f)); + const float text_baseline_y = ImTrunc((bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.99999f); //ImMax(padding.y, ImTrunc((style.SeparatorTextSize - label_size.y) * 0.5f)); ItemSize(min_size, text_baseline_y); if (!ItemAdd(bb, id)) return; @@ -1750,7 +1862,7 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF // Render shape const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); const float value_x2 = ImMax(bb.Min.x, bb.Max.x - arrow_size); - RenderNavHighlight(bb, id); + RenderNavCursor(bb, id); if (!(flags & ImGuiComboFlags_NoPreview)) window->DrawList->AddRectFilled(bb.Min, ImVec2(value_x2, bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft); if (!(flags & ImGuiComboFlags_NoArrowButton)) @@ -1840,7 +1952,7 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx() ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove; - PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(g.Style.FramePadding.x, g.Style.WindowPadding.y)); // Horizontally align ourselves with the framed text + PushStyleVarX(ImGuiStyleVar_WindowPadding, g.Style.FramePadding.x); // Horizontally align ourselves with the framed text bool ret = Begin(name, NULL, window_flags); PopStyleVar(); if (!ret) @@ -2059,6 +2171,7 @@ static const ImGuiDataTypeInfo GDataTypeInfo[] = #endif { sizeof(float), "float", "%.3f","%f" }, // ImGuiDataType_Float (float are promoted to double in va_arg) { sizeof(double), "double","%f", "%lf" }, // ImGuiDataType_Double + { sizeof(bool), "bool", "%d", "%d" }, // ImGuiDataType_Bool }; IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT); @@ -2249,6 +2362,12 @@ bool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_m return false; } +bool ImGui::DataTypeIsZero(ImGuiDataType data_type, const void* p_data) +{ + ImGuiContext& g = *GImGui; + return DataTypeCompare(data_type, p_data, &g.DataTypeZeroValue) == 0; +} + static float GetMinimumStepAtDecimalPrecision(int decimal_precision) { static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f }; @@ -2307,12 +2426,13 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const { ImGuiContext& g = *GImGui; const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X; - const bool is_clamped = (v_min < v_max); + const bool is_bounded = (v_min < v_max) || ((v_min == v_max) && (v_min != 0.0f || (flags & ImGuiSliderFlags_ClampZeroRange))); + const bool is_wrapped = is_bounded && (flags & ImGuiSliderFlags_WrapAround); const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0; const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); // Default tweak speed - if (v_speed == 0.0f && is_clamped && (v_max - v_min < FLT_MAX)) + if (v_speed == 0.0f && is_bounded && (v_max - v_min < FLT_MAX)) v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio); // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings @@ -2346,8 +2466,8 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const // Clear current value on activation // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300. - bool is_just_activated = g.ActiveIdIsJustActivated; - bool is_already_past_limits_and_pushing_outward = is_clamped && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f)); + const bool is_just_activated = g.ActiveIdIsJustActivated; + const bool is_already_past_limits_and_pushing_outward = is_bounded && !is_wrapped && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f)); if (is_just_activated || is_already_past_limits_and_pushing_outward) { g.DragCurrentAccum = 0.0f; @@ -2405,13 +2525,24 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const if (v_cur == (TYPE)-0) v_cur = (TYPE)0; - // Clamp values (+ handle overflow/wrap-around for integer types) - if (*v != v_cur && is_clamped) + if (*v != v_cur && is_bounded) { - if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point)) - v_cur = v_min; - if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point)) - v_cur = v_max; + if (is_wrapped) + { + // Wrap values + if (v_cur < v_min) + v_cur += v_max - v_min + (is_floating_point ? 0 : 1); + if (v_cur > v_max) + v_cur -= v_max - v_min + (is_floating_point ? 0 : 1); + } + else + { + // Clamp values + handle overflow/wrap-around for integer types. + if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point)) + v_cur = v_min; + if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point)) + v_cur = v_max; + } } // Apply result @@ -2424,7 +2555,7 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) { // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert. - IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); + IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); ImGuiContext& g = *GImGui; if (g.ActiveId == id) @@ -2437,7 +2568,7 @@ bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v } if (g.ActiveId != id) return false; - if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) + if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) return false; switch (data_type) @@ -2484,7 +2615,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; - const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags); + const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags); bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); if (!temp_input_is_active) { @@ -2518,14 +2649,22 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, if (temp_input_is_active) { - // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set - const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0 && (p_min == NULL || p_max == NULL || DataTypeCompare(data_type, p_min, p_max) < 0); - return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL); + // Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp) + bool clamp_enabled = false; + if ((flags & ImGuiSliderFlags_ClampOnInput) && (p_min != NULL || p_max != NULL)) + { + const int clamp_range_dir = (p_min != NULL && p_max != NULL) ? DataTypeCompare(data_type, p_min, p_max) : 0; // -1 when *p_min < *p_max, == 0 when *p_min == *p_max + if (p_min == NULL || p_max == NULL || clamp_range_dir < 0) + clamp_enabled = true; + else if (clamp_range_dir == 0) + clamp_enabled = DataTypeIsZero(data_type, p_min) ? ((flags & ImGuiSliderFlags_ClampZeroRange) != 0) : true; + } + return TempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL); } // Draw frame const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); - RenderNavHighlight(frame_bb, id); + RenderNavCursor(frame_bb, id); RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); // Drag behavior @@ -2916,14 +3055,14 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0; if (decimal_precision > 0) { - input_delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds + input_delta /= 100.0f; // Keyboard/Gamepad tweak speeds in % of slider bounds if (tweak_slow) input_delta /= 10.0f; } else { if ((v_range_f >= -100.0f && v_range_f <= 100.0f && v_range_f != 0.0f) || tweak_slow) - input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / v_range_f; // Gamepad/keyboard tweak speeds in integer steps + input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / v_range_f; // Keyboard/Gamepad tweak speeds in integer steps else input_delta /= 100.0f; } @@ -2971,7 +3110,7 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ } if (set_new_value) - if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) + if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) set_new_value = false; if (set_new_value) @@ -3017,7 +3156,8 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb) { // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert. - IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flag! Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); + IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); + IM_ASSERT((flags & ImGuiSliderFlags_WrapAround) == 0); // Not supported by SliderXXX(), only by DragXXX() switch (data_type) { @@ -3075,7 +3215,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; - const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags); + const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags); bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); if (!temp_input_is_active) { @@ -3099,14 +3239,14 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat if (temp_input_is_active) { - // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set - const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0; - return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL); + // Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp) + const bool clamp_enabled = (flags & ImGuiSliderFlags_ClampOnInput) != 0; + return TempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL); } // Draw frame const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); - RenderNavHighlight(frame_bb, id); + RenderNavCursor(frame_bb, id); RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); // Slider behavior @@ -3195,7 +3335,8 @@ bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, fl format = "%.0f deg"; float v_deg = (*v_rad) * 360.0f / (2 * IM_PI); bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags); - *v_rad = v_deg * (2 * IM_PI) / 360.0f; + if (value_changed) + *v_rad = v_deg * (2 * IM_PI) / 360.0f; return value_changed; } @@ -3241,7 +3382,7 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; - const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags); + const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags); const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id); if (clicked || g.NavActivateId == id) { @@ -3255,7 +3396,7 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d // Draw frame const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); - RenderNavHighlight(frame_bb, id); + RenderNavCursor(frame_bb, id); RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); // Slider behavior @@ -3437,6 +3578,8 @@ int ImParseFormatPrecision(const char* fmt, int default_precision) // Create text input in place of another active widget (e.g. used when doing a CTRL+Click on drag/slider widgets) // FIXME: Facilitate using this in variety of other situations. +// FIXME: Among other things, setting ImGuiItemFlags_AllowDuplicateId in LastItemData is currently correct but +// the expected relationship between TempInputXXX functions and LastItemData is a little fishy. bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags) { // On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id. @@ -3447,6 +3590,7 @@ bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* ClearActiveID(); g.CurrentWindow->DC.CursorPos = bb.Min; + g.LastItemData.ItemFlags |= ImGuiItemFlags_AllowDuplicateId; bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_MergedItem); if (init) { @@ -3464,6 +3608,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG { // FIXME: May need to clarify display behavior if format doesn't contain %. // "%d" -> "%d" / "There are %d items" -> "%d" / "items" -> "%d" (fallback). Also see #6405 + ImGuiContext& g = *GImGui; const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type); char fmt_buf[32]; char data_buf[32]; @@ -3473,8 +3618,8 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format); ImStrTrimBlanks(data_buf); - ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_NoMarkEdited | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; - + ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; + g.LastItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; // Because TempInputText() uses ImGuiInputTextFlags_MergedItem it doesn't submit a new item, so we poke LastItemData. bool value_changed = false; if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags)) { @@ -3493,6 +3638,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG } // Only mark as edited if new value is different + g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited; value_changed = memcmp(&data_backup, p_data, data_type_size) != 0; if (value_changed) MarkItemEdited(id); @@ -3503,7 +3649,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG void ImGui::SetNextItemRefVal(ImGuiDataType data_type, void* p_data) { ImGuiContext& g = *GImGui; - g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasRefVal; + g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasRefVal; memcpy(&g.NextItemData.RefVal, p_data, DataTypeGetInfo(data_type)->Size); } @@ -3517,11 +3663,12 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; + IM_ASSERT((flags & ImGuiInputTextFlags_EnterReturnsTrue) == 0); // Not supported by InputScalar(). Please open an issue if you this would be useful to you. Otherwise use IsItemDeactivatedAfterEdit()! if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; - void* p_data_default = (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasRefVal) ? &g.NextItemData.RefVal : &g.DataTypeZeroValue; + void* p_data_default = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasRefVal) ? &g.NextItemData.RefVal : &g.DataTypeZeroValue; char buf[64]; if ((flags & ImGuiInputTextFlags_DisplayEmptyRefVal) && DataTypeCompare(data_type, p_data, p_data_default) == 0) @@ -3529,8 +3676,10 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data else DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format); - flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_NoMarkEdited; // We call MarkItemEdited() ourselves by comparing the actual data rather than the string. - flags |= (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; + // Disable the MarkItemEdited() call in InputText but keep ImGuiItemStatusFlags_Edited. + // We call MarkItemEdited() ourselves by comparing the actual data rather than the string. + g.NextItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; + flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; bool value_changed = false; if (p_step == NULL) @@ -3552,21 +3701,22 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data // Step buttons const ImVec2 backup_frame_padding = style.FramePadding; style.FramePadding.x = style.FramePadding.y; - ImGuiButtonFlags button_flags = ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups; if (flags & ImGuiInputTextFlags_ReadOnly) BeginDisabled(); + PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); SameLine(0, style.ItemInnerSpacing.x); - if (ButtonEx("-", ImVec2(button_size, button_size), button_flags)) + if (ButtonEx("-", ImVec2(button_size, button_size))) { DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); value_changed = true; } SameLine(0, style.ItemInnerSpacing.x); - if (ButtonEx("+", ImVec2(button_size, button_size), button_flags)) + if (ButtonEx("+", ImVec2(button_size, button_size))) { DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); value_changed = true; } + PopItemFlag(); if (flags & ImGuiInputTextFlags_ReadOnly) EndDisabled(); @@ -3581,6 +3731,8 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data PopID(); EndGroup(); } + + g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited; if (value_changed) MarkItemEdited(g.LastItemData.ID); @@ -3672,6 +3824,7 @@ bool ImGui::InputDouble(const char* label, double* v, double step, double step_f //------------------------------------------------------------------------- // [SECTION] Widgets: InputText, InputTextMultiline, InputTextWithHint //------------------------------------------------------------------------- +// - imstb_textedit.h include // - InputText() // - InputTextWithHint() // - InputTextMultiline() @@ -3682,6 +3835,11 @@ bool ImGui::InputDouble(const char* label, double* v, double step, double step_f // - DebugNodeInputTextState() [Internal] //------------------------------------------------------------------------- +namespace ImStb +{ +#include "imstb_textedit.h" +} + bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() @@ -3699,21 +3857,28 @@ bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, si return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data); } +// This is only used in the path where the multiline widget is inactivate. static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end) { int line_count = 0; const char* s = text_begin; - while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding - if (c == '\n') - line_count++; - s--; - if (s[0] != '\n' && s[0] != '\r') + while (true) + { + const char* s_eol = strchr(s, '\n'); line_count++; + if (s_eol == NULL) + { + s = s + strlen(s); + break; + } + s = s_eol + 1; + } *out_text_end = s; return line_count; } -static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line) +// FIXME: Ideally we'd share code with ImFont::CalcTextSizeA() +static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining, ImVec2* out_offset, bool stop_on_new_line) { ImGuiContext& g = *ctx; ImFont* font = g.Font; @@ -3723,10 +3888,15 @@ static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begi ImVec2 text_size = ImVec2(0, 0); float line_width = 0.0f; - const ImWchar* s = text_begin; + const char* s = text_begin; while (s < text_end) { - unsigned int c = (unsigned int)(*s++); + unsigned int c = (unsigned int)*s; + if (c < 0x80) + s += 1; + else + s += ImTextCharFromUtf8(&c, s, text_end); + if (c == '\n') { text_size.x = ImMax(text_size.x, line_width); @@ -3739,7 +3909,7 @@ static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begi if (c == '\r') continue; - const float char_width = font->GetCharAdvance((ImWchar)c) * scale; + const float char_width = ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX) * scale; line_width += char_width; } @@ -3759,19 +3929,21 @@ static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begi } // Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar) +// With our UTF-8 use of stb_textedit: +// - STB_TEXTEDIT_GETCHAR is nothing more than a a "GETBYTE". It's only used to compare to ascii or to copy blocks of text so we are fine. +// - One exception is the STB_TEXTEDIT_IS_SPACE feature which would expect a full char in order to handle full-width space such as 0x3000 (see ImCharIsBlankW). +// - ...but we don't use that feature. namespace ImStb { - -static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->CurLenW; } -static ImWchar STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx <= obj->CurLenW); return obj->TextW[idx]; } -static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { ImWchar c = obj->TextW[line_start_idx + char_idx]; if (c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance(c) * (g.FontSize / g.Font->FontSize); } -static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x200000 ? 0 : key; } -static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; +static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->TextLen; } +static char STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx <= obj->TextLen); return obj->TextA[idx]; } +static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextA.Data + line_start_idx + char_idx, obj->TextA.Data + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance((ImWchar)c) * g.FontScale; } +static char STB_TEXTEDIT_NEWLINE = '\n'; static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx) { - const ImWchar* text = obj->TextW.Data; - const ImWchar* text_remaining = NULL; - const ImVec2 size = InputTextCalcTextSizeW(obj->Ctx, text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true); + const char* text = obj->TextA.Data; + const char* text_remaining = NULL; + const ImVec2 size = InputTextCalcTextSize(obj->Ctx, text + line_start_idx, text + obj->TextLen, &text_remaining, NULL, true); r->x0 = 0.0f; r->x1 = size.x; r->baseline_y_delta = size.y; @@ -3780,9 +3952,37 @@ static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* ob r->num_chars = (int)(text_remaining - (text + line_start_idx)); } -static bool is_separator(unsigned int c) +#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL +#define IMSTB_TEXTEDIT_GETPREVCHARINDEX IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL + +static int IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx) +{ + if (idx >= obj->TextLen) + return obj->TextLen + 1; + unsigned int c; + return idx + ImTextCharFromUtf8(&c, obj->TextA.Data + idx, obj->TextA.Data + obj->TextLen); +} + +static int IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx) { - return c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|' || c=='\n' || c=='\r' || c=='.' || c=='!'; + if (idx <= 0) + return -1; + const char* p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, obj->TextA.Data + idx); + return (int)(p - obj->TextA.Data); +} + +static bool ImCharIsSeparatorW(unsigned int c) +{ + static const unsigned int separator_list[] = + { + ',', 0x3001, '.', 0x3002, ';', 0xFF1B, '(', 0xFF08, ')', 0xFF09, '{', 0xFF5B, '}', 0xFF5D, + '[', 0x300C, ']', 0x300D, '|', 0xFF5C, '!', 0xFF01, '\\', 0xFFE5, '/', 0x30FB, 0xFF0F, + '\n', '\r', + }; + for (unsigned int separator : separator_list) + if (c == separator) + return true; + return false; } static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx) @@ -3791,10 +3991,15 @@ static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx) if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0) return 0; - bool prev_white = ImCharIsBlankW(obj->TextW[idx - 1]); - bool prev_separ = is_separator(obj->TextW[idx - 1]); - bool curr_white = ImCharIsBlankW(obj->TextW[idx]); - bool curr_separ = is_separator(obj->TextW[idx]); + const char* curr_p = obj->TextA.Data + idx; + const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, curr_p); + unsigned int curr_c; ImTextCharFromUtf8(&curr_c, curr_p, obj->TextA.Data + obj->TextLen); + unsigned int prev_c; ImTextCharFromUtf8(&prev_c, prev_p, obj->TextA.Data + obj->TextLen); + + bool prev_white = ImCharIsBlankW(prev_c); + bool prev_separ = ImCharIsSeparatorW(prev_c); + bool curr_white = ImCharIsBlankW(curr_c); + bool curr_separ = ImCharIsSeparatorW(curr_c); return ((prev_white || prev_separ) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ); } static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx) @@ -3802,63 +4007,83 @@ static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx) if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0) return 0; - bool prev_white = ImCharIsBlankW(obj->TextW[idx]); - bool prev_separ = is_separator(obj->TextW[idx]); - bool curr_white = ImCharIsBlankW(obj->TextW[idx - 1]); - bool curr_separ = is_separator(obj->TextW[idx - 1]); + const char* curr_p = obj->TextA.Data + idx; + const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, curr_p); + unsigned int prev_c; ImTextCharFromUtf8(&prev_c, curr_p, obj->TextA.Data + obj->TextLen); + unsigned int curr_c; ImTextCharFromUtf8(&curr_c, prev_p, obj->TextA.Data + obj->TextLen); + + bool prev_white = ImCharIsBlankW(prev_c); + bool prev_separ = ImCharIsSeparatorW(prev_c); + bool curr_white = ImCharIsBlankW(curr_c); + bool curr_separ = ImCharIsSeparatorW(curr_c); return ((prev_white) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ); } -static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; } -static int STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; } -static int STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; } +static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx) +{ + idx = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx); + while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) + idx = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx); + return idx < 0 ? 0 : idx; +} +static int STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState* obj, int idx) +{ + int len = obj->TextLen; + idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx); + while (idx < len && !is_word_boundary_from_left(obj, idx)) + idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx); + return idx > len ? len : idx; +} +static int STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState* obj, int idx) +{ + idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx); + int len = obj->TextLen; + while (idx < len && !is_word_boundary_from_right(obj, idx)) + idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx); + return idx > len ? len : idx; +} static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx) { ImGuiContext& g = *obj->Ctx; if (g.IO.ConfigMacOSXBehaviors) return STB_TEXTEDIT_MOVEWORDRIGHT_MAC(obj, idx); else return STB_TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx); } -#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h -#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL +#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h +#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n) { - ImWchar* dst = obj->TextW.Data + pos; + char* dst = obj->TextA.Data + pos; - // We maintain our buffer length in both UTF-8 and wchar formats obj->Edited = true; - obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n); - obj->CurLenW -= n; + obj->TextLen -= n; // Offset remaining text (FIXME-OPT: Use memmove) - const ImWchar* src = obj->TextW.Data + pos + n; - while (ImWchar c = *src++) + const char* src = obj->TextA.Data + pos + n; + while (char c = *src++) *dst++ = c; *dst = '\0'; } -static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const ImWchar* new_text, int new_text_len) +static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const char* new_text, int new_text_len) { const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0; - const int text_len = obj->CurLenW; + const int text_len = obj->TextLen; IM_ASSERT(pos <= text_len); - const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len); - if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA)) + if (!is_resizable && (new_text_len + obj->TextLen + 1 > obj->BufCapacity)) return false; // Grow internal buffer if needed - if (new_text_len + text_len + 1 > obj->TextW.Size) + if (new_text_len + text_len + 1 > obj->TextA.Size) { if (!is_resizable) return false; - IM_ASSERT(text_len < obj->TextW.Size); - obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1); + obj->TextA.resize(text_len + ImClamp(new_text_len, 32, ImMax(256, new_text_len)) + 1); } - ImWchar* text = obj->TextW.Data; + char* text = obj->TextA.Data; if (pos != text_len) - memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar)); - memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar)); + memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos)); + memcpy(text + pos, new_text, (size_t)new_text_len); obj->Edited = true; - obj->CurLenW += new_text_len; - obj->CurLenA += new_text_len_utf8; - obj->TextW[obj->CurLenW] = '\0'; + obj->TextLen += new_text_len; + obj->TextA[obj->TextLen] = '\0'; return true; } @@ -3890,8 +4115,8 @@ static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const Im // the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?) static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len) { - stb_text_makeundo_replace(str, state, 0, str->CurLenW, text_len); - ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->CurLenW); + stb_text_makeundo_replace(str, state, 0, str->TextLen, text_len); + ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->TextLen); state->cursor = state->select_start = state->select_end = 0; if (text_len <= 0) return; @@ -3906,13 +4131,50 @@ static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* st } // namespace ImStb +// We added an extra indirection where 'Stb' is heap-allocated, in order facilitate the work of bindings generators. +ImGuiInputTextState::ImGuiInputTextState() +{ + memset(this, 0, sizeof(*this)); + Stb = IM_NEW(ImStbTexteditState); + memset(Stb, 0, sizeof(*Stb)); +} + +ImGuiInputTextState::~ImGuiInputTextState() +{ + IM_DELETE(Stb); +} + void ImGuiInputTextState::OnKeyPressed(int key) { - stb_textedit_key(this, &Stb, key); + stb_textedit_key(this, Stb, key); + CursorFollow = true; + CursorAnimReset(); +} + +void ImGuiInputTextState::OnCharPressed(unsigned int c) +{ + // Convert the key to a UTF8 byte sequence. + // The changes we had to make to stb_textedit_key made it very much UTF-8 specific which is not too great. + char utf8[5]; + ImTextCharToUtf8(utf8, c); + stb_textedit_text(this, Stb, utf8, (int)strlen(utf8)); CursorFollow = true; CursorAnimReset(); } +// Those functions are not inlined in imgui_internal.h, allowing us to hide ImStbTexteditState from that header. +void ImGuiInputTextState::CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking +void ImGuiInputTextState::CursorClamp() { Stb->cursor = ImMin(Stb->cursor, TextLen); Stb->select_start = ImMin(Stb->select_start, TextLen); Stb->select_end = ImMin(Stb->select_end, TextLen); } +bool ImGuiInputTextState::HasSelection() const { return Stb->select_start != Stb->select_end; } +void ImGuiInputTextState::ClearSelection() { Stb->select_start = Stb->select_end = Stb->cursor; } +int ImGuiInputTextState::GetCursorPos() const { return Stb->cursor; } +int ImGuiInputTextState::GetSelectionStart() const { return Stb->select_start; } +int ImGuiInputTextState::GetSelectionEnd() const { return Stb->select_end; } +void ImGuiInputTextState::SelectAll() { Stb->select_start = 0; Stb->cursor = Stb->select_end = TextLen; Stb->has_preferred_x = 0; } +void ImGuiInputTextState::ReloadUserBufAndSelectAll() { ReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; } +void ImGuiInputTextState::ReloadUserBufAndKeepSelection() { ReloadUserBuf = true; ReloadSelectionStart = Stb->select_start; ReloadSelectionEnd = Stb->select_end; } +void ImGuiInputTextState::ReloadUserBufAndMoveToEnd() { ReloadUserBuf = true; ReloadSelectionStart = ReloadSelectionEnd = INT_MAX; } + ImGuiInputTextCallbackData::ImGuiInputTextCallbackData() { memset(this, 0, sizeof(*this)); @@ -3945,6 +4207,7 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons if (new_text == new_text_end) return; + // Grow internal buffer if needed const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0; const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text); if (new_text_len + BufTextLen >= BufSize) @@ -3958,9 +4221,9 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID); IM_ASSERT(Buf == edit_state->TextA.Data); int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1; - edit_state->TextA.reserve(new_buf_size + 1); + edit_state->TextA.resize(new_buf_size + 1); Buf = edit_state->TextA.Data; - BufSize = edit_state->BufCapacityA = new_buf_size; + BufSize = edit_state->BufCapacity = new_buf_size; } if (BufTextLen != pos) @@ -4014,10 +4277,10 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im // The standard mandate that programs starts in the "C" locale where the decimal point is '.'. // We don't really intend to provide widespread support for it, but out of empathy for people stuck with using odd API, we support the bare minimum aka overriding the decimal point. // Change the default decimal_point with: - // ImGui::GetIO()->PlatformLocaleDecimalPoint = *localeconv()->decimal_point; + // ImGui::GetPlatformIO()->Platform_LocaleDecimalPoint = *localeconv()->decimal_point; // Users of non-default decimal point (in particular ',') may be affected by word-selection logic (is_word_boundary_from_right/is_word_boundary_from_left) functions. ImGuiContext& g = *ctx; - const unsigned c_decimal_point = (unsigned int)g.IO.PlatformLocaleDecimalPoint; + const unsigned c_decimal_point = (unsigned int)g.PlatformIO.Platform_LocaleDecimalPoint; if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint)) if (c == '.' || c == ',') c = c_decimal_point; @@ -4081,34 +4344,29 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im // FIXME: Ideally we should transition toward (1) making InsertChars()/DeleteChars() update undo-stack (2) discourage (and keep reconcile) or obsolete (and remove reconcile) accessing buffer directly. static void InputTextReconcileUndoStateAfterUserCallback(ImGuiInputTextState* state, const char* new_buf_a, int new_length_a) { - ImGuiContext& g = *GImGui; - const ImWchar* old_buf = state->TextW.Data; - const int old_length = state->CurLenW; - const int new_length = ImTextCountCharsFromUtf8(new_buf_a, new_buf_a + new_length_a); - g.TempBuffer.reserve_discard((new_length + 1) * sizeof(ImWchar)); - ImWchar* new_buf = (ImWchar*)(void*)g.TempBuffer.Data; - ImTextStrFromUtf8(new_buf, new_length + 1, new_buf_a, new_buf_a + new_length_a); - - const int shorter_length = ImMin(old_length, new_length); + const char* old_buf = state->CallbackTextBackup.Data; + const int old_length = state->CallbackTextBackup.Size - 1; + + const int shorter_length = ImMin(old_length, new_length_a); int first_diff; for (first_diff = 0; first_diff < shorter_length; first_diff++) - if (old_buf[first_diff] != new_buf[first_diff]) + if (old_buf[first_diff] != new_buf_a[first_diff]) break; - if (first_diff == old_length && first_diff == new_length) + if (first_diff == old_length && first_diff == new_length_a) return; - int old_last_diff = old_length - 1; - int new_last_diff = new_length - 1; + int old_last_diff = old_length - 1; + int new_last_diff = new_length_a - 1; for (; old_last_diff >= first_diff && new_last_diff >= first_diff; old_last_diff--, new_last_diff--) - if (old_buf[old_last_diff] != new_buf[new_last_diff]) + if (old_buf[old_last_diff] != new_buf_a[new_last_diff]) break; const int insert_len = new_last_diff - first_diff + 1; const int delete_len = old_last_diff - first_diff + 1; if (insert_len > 0 || delete_len > 0) - if (IMSTB_TEXTEDIT_CHARTYPE* p = stb_text_createundo(&state->Stb.undostate, first_diff, delete_len, insert_len)) + if (IMSTB_TEXTEDIT_CHARTYPE* p = stb_text_createundo(&state->Stb->undostate, first_diff, delete_len, insert_len)) for (int i = 0; i < delete_len; i++) - p[i] = ImStb::STB_TEXTEDIT_GETCHAR(state, first_diff + i); + p[i] = old_buf[first_diff + i]; } // As InputText() retain textual data and we currently provide a path for user to not retain it (via local variables) @@ -4129,8 +4387,8 @@ void ImGui::InputTextDeactivateHook(ImGuiID id) else { IM_ASSERT(state->TextA.Data != 0); - g.InputTextDeactivatedState.TextA.resize(state->CurLenA + 1); - memcpy(g.InputTextDeactivatedState.TextA.Data, state->TextA.Data, state->CurLenA + 1); + g.InputTextDeactivatedState.TextA.resize(state->TextLen + 1); + memcpy(g.InputTextDeactivatedState.TextA.Data, state->TextA.Data, state->TextLen + 1); } } @@ -4151,6 +4409,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ IM_ASSERT(buf != NULL && buf_size >= 0); IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys) IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key) + IM_ASSERT(!((flags & ImGuiInputTextFlags_ElideLeft) && (flags & ImGuiInputTextFlags_Multiline))); // Multiline will not work with left-trimming ImGuiContext& g = *GImGui; ImGuiIO& io = g.IO; @@ -4198,7 +4457,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding); PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize); PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); // Ensure no clip rect so mouse hover can reach FramePadding edges - bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), true, ImGuiWindowFlags_NoMove); + bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove); g.NavActivateId = backup_activate_id; PopStyleVar(3); PopStyleColor(); @@ -4221,14 +4480,18 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable)) return false; } - const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags); + + // Ensure mouse cursor is set even after switching to keyboard/gamepad mode. May generalize further? (#6417) + bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags | ImGuiItemFlags_NoNavDisableMouseHover); if (hovered) - g.MouseCursor = ImGuiMouseCursor_TextInput; + SetMouseCursor(ImGuiMouseCursor_TextInput); + if (hovered && g.NavHighlightItemUnderNav) + hovered = false; // We are only allowed to access the state if we are already the active widget. ImGuiInputTextState* state = GetInputTextState(id); - if (g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) + if (g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) flags |= ImGuiInputTextFlags_ReadOnly; const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0; const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; @@ -4248,7 +4511,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX; const bool init_reload_from_user_buf = (state != NULL && state->ReloadUserBuf); - const bool init_changed_specs = (state != NULL && state->Stb.single_line != !is_multiline); // state != NULL means its our state. + const bool init_changed_specs = (state != NULL && state->Stb->single_line != !is_multiline); // state != NULL means its our state. const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav); const bool init_state = (init_make_active || user_scroll_active); if ((init_state && g.ActiveId != id) || init_changed_specs || init_reload_from_user_buf) @@ -4266,24 +4529,26 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (!init_reload_from_user_buf) { // Take a copy of the initial buffer value. - state->InitialTextA.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string. - memcpy(state->InitialTextA.Data, buf, buf_len + 1); + state->TextToRevertTo.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string. + memcpy(state->TextToRevertTo.Data, buf, buf_len + 1); } // Preserve cursor position and undo/redo stack if we come back to same widget // FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate? bool recycle_state = (state->ID == id && !init_changed_specs && !init_reload_from_user_buf); - if (recycle_state && (state->CurLenA != buf_len || (state->TextAIsValid && strncmp(state->TextA.Data, buf, buf_len) != 0))) + if (recycle_state && (state->TextLen != buf_len || (strncmp(state->TextA.Data, buf, buf_len) != 0))) recycle_state = false; // Start edition - const char* buf_end = NULL; state->ID = id; - state->TextW.resize(buf_size + 1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string. - state->TextA.resize(0); - state->TextAIsValid = false; // TextA is not valid yet (we will display buf until then) - state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end); - state->CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. + state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string. + state->TextLen = (int)strlen(buf); + memcpy(state->TextA.Data, buf, state->TextLen + 1); + + // Find initial scroll position for right alignment + state->Scroll = ImVec2(0.0f, 0.0f); + if (flags & ImGuiInputTextFlags_ElideLeft) + state->Scroll.x += ImMax(0.0f, CalcTextSize(buf).x - frame_size.x + style.FramePadding.x * 2.0f); if (recycle_state) { @@ -4293,14 +4558,13 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } else { - state->ScrollX = 0.0f; - stb_textedit_initialize_state(&state->Stb, !is_multiline); + stb_textedit_initialize_state(state->Stb, !is_multiline); } if (init_reload_from_user_buf) { - state->Stb.select_start = state->ReloadSelectionStart; - state->Stb.cursor = state->Stb.select_end = state->ReloadSelectionEnd; + state->Stb->select_start = state->ReloadSelectionStart; + state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd; state->CursorClamp(); } else if (!is_multiline) @@ -4314,7 +4578,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } if (flags & ImGuiInputTextFlags_AlwaysOverwrite) - state->Stb.insert_mode = 1; // stb field name is indeed incorrect (see #2863) + state->Stb->insert_mode = 1; // stb field name is indeed incorrect (see #2863) } const bool is_osx = io.ConfigMacOSXBehaviors; @@ -4328,15 +4592,19 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (g.ActiveId == id) { // Declare some inputs, the other are registered and polled via Shortcut() routing system. + // FIXME: The reason we don't use Shortcut() is we would need a routing flag to specify multiple mods, or to all mods combinaison into individual shortcuts. + const ImGuiKey always_owned_keys[] = { ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_Enter, ImGuiKey_KeypadEnter, ImGuiKey_Delete, ImGuiKey_Backspace, ImGuiKey_Home, ImGuiKey_End }; + for (ImGuiKey key : always_owned_keys) + SetKeyOwner(key, id); if (user_clicked) SetKeyOwner(ImGuiKey_MouseLeft, id); g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory)) + { g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); - SetKeyOwner(ImGuiKey_Enter, id); - SetKeyOwner(ImGuiKey_KeypadEnter, id); - SetKeyOwner(ImGuiKey_Home, id); - SetKeyOwner(ImGuiKey_End, id); + SetKeyOwner(ImGuiKey_UpArrow, id); + SetKeyOwner(ImGuiKey_DownArrow, id); + } if (is_multiline) { SetKeyOwner(ImGuiKey_PageUp, id); @@ -4345,6 +4613,10 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // FIXME: May be a problem to always steal Alt on OSX, would ideally still allow an uninterrupted Alt down-up to toggle menu if (is_osx) SetKeyOwner(ImGuiMod_Alt, id); + + // Expose scroll in a manner that is agnostic to us using a child window + if (is_multiline && state != NULL) + state->Scroll.y = draw_window->Scroll.y; } // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function) @@ -4361,20 +4633,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ bool value_changed = false; bool validated = false; - // When read-only we always use the live data passed to the function - // FIXME-OPT: Because our selection/cursor code currently needs the wide text we need to convert it when active, which is not ideal :( - if (is_readonly && state != NULL && (render_cursor || render_selection)) - { - const char* buf_end = NULL; - state->TextW.resize(buf_size + 1); - state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, buf, NULL, &buf_end); - state->CurLenA = (int)(buf_end - buf); - state->CursorClamp(); - render_selection &= state->HasSelection(); - } - // Select the buffer to render. - const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state && state->TextAIsValid; + const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state; const bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0); // Password pushes a temporary font with only a fallback glyph @@ -4394,13 +4654,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } // Process mouse inputs and character inputs - int backup_current_text_length = 0; if (g.ActiveId == id) { IM_ASSERT(state != NULL); - backup_current_text_length = state->CurLenA; state->Edited = false; - state->BufCapacityA = buf_size; + state->BufCapacity = buf_size; state->Flags = flags; // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. @@ -4408,7 +4666,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ g.ActiveIdAllowOverlap = !io.MouseDown[0]; // Edit in progress - const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->ScrollX; + const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->Scroll.x; const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y) : (g.FontSize * 0.5f)); if (select_all) @@ -4418,34 +4676,34 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } else if (hovered && io.MouseClickedCount[0] >= 2 && !io.KeyShift) { - stb_textedit_click(state, &state->Stb, mouse_x, mouse_y); + stb_textedit_click(state, state->Stb, mouse_x, mouse_y); const int multiclick_count = (io.MouseClickedCount[0] - 2); if ((multiclick_count % 2) == 0) { // Double-click: Select word // We always use the "Mac" word advance for double-click select vs CTRL+Right which use the platform dependent variant: // FIXME: There are likely many ways to improve this behavior, but there's no "right" behavior (depends on use-case, software, OS) - const bool is_bol = (state->Stb.cursor == 0) || ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb.cursor - 1) == '\n'; - if (STB_TEXT_HAS_SELECTION(&state->Stb) || !is_bol) + const bool is_bol = (state->Stb->cursor == 0) || ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor - 1) == '\n'; + if (STB_TEXT_HAS_SELECTION(state->Stb) || !is_bol) state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); //state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); - if (!STB_TEXT_HAS_SELECTION(&state->Stb)) - ImStb::stb_textedit_prep_selection_at_cursor(&state->Stb); - state->Stb.cursor = ImStb::STB_TEXTEDIT_MOVEWORDRIGHT_MAC(state, state->Stb.cursor); - state->Stb.select_end = state->Stb.cursor; - ImStb::stb_textedit_clamp(state, &state->Stb); + if (!STB_TEXT_HAS_SELECTION(state->Stb)) + ImStb::stb_textedit_prep_selection_at_cursor(state->Stb); + state->Stb->cursor = ImStb::STB_TEXTEDIT_MOVEWORDRIGHT_MAC(state, state->Stb->cursor); + state->Stb->select_end = state->Stb->cursor; + ImStb::stb_textedit_clamp(state, state->Stb); } else { // Triple-click: Select line - const bool is_eol = ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb.cursor) == '\n'; + const bool is_eol = ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor) == '\n'; state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART); state->OnKeyPressed(STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT); state->OnKeyPressed(STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT); if (!is_eol && is_multiline) { - ImSwap(state->Stb.select_start, state->Stb.select_end); - state->Stb.cursor = state->Stb.select_end; + ImSwap(state->Stb->select_start, state->Stb->select_end); + state->Stb->cursor = state->Stb->select_end; } state->CursorFollow = false; } @@ -4456,15 +4714,15 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (hovered) { if (io.KeyShift) - stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y); + stb_textedit_drag(state, state->Stb, mouse_x, mouse_y); else - stb_textedit_click(state, &state->Stb, mouse_x, mouse_y); + stb_textedit_click(state, state->Stb, mouse_x, mouse_y); state->CursorAnimReset(); } } else if (io.MouseDown[0] && !state->SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) { - stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y); + stb_textedit_drag(state, state->Stb, mouse_x, mouse_y); state->CursorAnimReset(); state->CursorFollow = true; } @@ -4479,7 +4737,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { unsigned int c = '\t'; // Insert TAB if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data)) - state->OnKeyPressed((int)c); + state->OnCharPressed(c); } // FIXME: Implement Shift+Tab /* @@ -4502,7 +4760,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (c == '\t') // Skip Tab, see above. continue; if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data)) - state->OnKeyPressed((int)c); + state->OnCharPressed(c); } // Consume characters @@ -4517,7 +4775,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ IM_ASSERT(state != NULL); const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1); - state->Stb.row_count_per_page = row_count_per_page; + state->Stb->row_count_per_page = row_count_per_page; const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl @@ -4586,7 +4844,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { unsigned int c = '\n'; // Insert new line if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data)) - state->OnKeyPressed((int)c); + state->OnCharPressed(c); } } else if (is_cancel) @@ -4622,22 +4880,22 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ else if (is_cut || is_copy) { // Cut, Copy - if (io.SetClipboardTextFn) + if (g.PlatformIO.Platform_SetClipboardTextFn != NULL) { - const int ib = state->HasSelection() ? ImMin(state->Stb.select_start, state->Stb.select_end) : 0; - const int ie = state->HasSelection() ? ImMax(state->Stb.select_start, state->Stb.select_end) : state->CurLenW; - const int clipboard_data_len = ImTextCountUtf8BytesFromStr(state->TextW.Data + ib, state->TextW.Data + ie) + 1; - char* clipboard_data = (char*)IM_ALLOC(clipboard_data_len * sizeof(char)); - ImTextStrToUtf8(clipboard_data, clipboard_data_len, state->TextW.Data + ib, state->TextW.Data + ie); - SetClipboardText(clipboard_data); - MemFree(clipboard_data); + const int ib = state->HasSelection() ? ImMin(state->Stb->select_start, state->Stb->select_end) : 0; + const int ie = state->HasSelection() ? ImMax(state->Stb->select_start, state->Stb->select_end) : state->TextLen; + + char backup = state->TextA.Data[ie]; + state->TextA.Data[ie] = 0; // A bit of a hack since SetClipboardText only takes null terminated strings + SetClipboardText(state->TextA.Data + ib); + state->TextA.Data[ie] = backup; } if (is_cut) { if (!state->HasSelection()) state->SelectAll(); state->CursorFollow = true; - stb_textedit_cut(state, &state->Stb); + stb_textedit_cut(state, state->Stb); } } else if (is_paste) @@ -4646,20 +4904,22 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { // Filter pasted buffer const int clipboard_len = (int)strlen(clipboard); - ImWchar* clipboard_filtered = (ImWchar*)IM_ALLOC((clipboard_len + 1) * sizeof(ImWchar)); + char* clipboard_filtered = (char*)IM_ALLOC(clipboard_len + 1); int clipboard_filtered_len = 0; for (const char* s = clipboard; *s != 0; ) { unsigned int c; - s += ImTextCharFromUtf8(&c, s, NULL); + int len = ImTextCharFromUtf8(&c, s, NULL); + s += len; if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, true)) continue; - clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c; + memcpy(clipboard_filtered + clipboard_filtered_len, s - len, len); + clipboard_filtered_len += len; } clipboard_filtered[clipboard_filtered_len] = 0; if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation { - stb_textedit_paste(state, &state->Stb, clipboard_filtered, clipboard_filtered_len); + stb_textedit_paste(state, state->Stb, clipboard_filtered, clipboard_filtered_len); state->CursorFollow = true; } MemFree(clipboard_filtered); @@ -4686,33 +4946,20 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ apply_new_text_length = 0; value_changed = true; IMSTB_TEXTEDIT_CHARTYPE empty_string; - stb_textedit_replace(state, &state->Stb, &empty_string, 0); + stb_textedit_replace(state, state->Stb, &empty_string, 0); } - else if (strcmp(buf, state->InitialTextA.Data) != 0) + else if (strcmp(buf, state->TextToRevertTo.Data) != 0) { + apply_new_text = state->TextToRevertTo.Data; + apply_new_text_length = state->TextToRevertTo.Size - 1; + // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents. // Push records into the undo stack so we can CTRL+Z the revert operation itself - apply_new_text = state->InitialTextA.Data; - apply_new_text_length = state->InitialTextA.Size - 1; value_changed = true; - ImVector w_text; - if (apply_new_text_length > 0) - { - w_text.resize(ImTextCountCharsFromUtf8(apply_new_text, apply_new_text + apply_new_text_length) + 1); - ImTextStrFromUtf8(w_text.Data, w_text.Size, apply_new_text, apply_new_text + apply_new_text_length); - } - stb_textedit_replace(state, &state->Stb, w_text.Data, (apply_new_text_length > 0) ? (w_text.Size - 1) : 0); + stb_textedit_replace(state, state->Stb, state->TextToRevertTo.Data, state->TextToRevertTo.Size - 1); } } - // Apply ASCII value - if (!is_readonly) - { - state->TextAIsValid = true; - state->TextA.resize(state->TextW.Size * 4 + 1); - ImTextStrToUtf8(state->TextA.Data, state->TextA.Size, state->TextW.Data, NULL); - } - // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer // before clearing ActiveId, even though strictly speaking it wasn't modified on this frame. // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. @@ -4767,18 +5014,20 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ callback_data.Flags = flags; callback_data.UserData = callback_user_data; + // FIXME-OPT: Undo stack reconcile needs a backup of the data until we rework API, see #7925 + state->CallbackTextBackup.resize(state->TextLen + 1); + memcpy(state->CallbackTextBackup.Data, state->TextA.Data, state->TextLen + 1); + char* callback_buf = is_readonly ? buf : state->TextA.Data; callback_data.EventKey = event_key; callback_data.Buf = callback_buf; - callback_data.BufTextLen = state->CurLenA; - callback_data.BufSize = state->BufCapacityA; + callback_data.BufTextLen = state->TextLen; + callback_data.BufSize = state->BufCapacity; callback_data.BufDirty = false; - // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188) - ImWchar* text = state->TextW.Data; - const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + state->Stb.cursor); - const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_start); - const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_end); + const int utf8_cursor_pos = callback_data.CursorPos = state->Stb->cursor; + const int utf8_selection_start = callback_data.SelectionStart = state->Stb->select_start; + const int utf8_selection_end = callback_data.SelectionEnd = state->Stb->select_end; // Call user code callback(&callback_data); @@ -4786,21 +5035,18 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Read back what user may have modified callback_buf = is_readonly ? buf : state->TextA.Data; // Pointer may have been invalidated by a resize callback IM_ASSERT(callback_data.Buf == callback_buf); // Invalid to modify those fields - IM_ASSERT(callback_data.BufSize == state->BufCapacityA); + IM_ASSERT(callback_data.BufSize == state->BufCapacity); IM_ASSERT(callback_data.Flags == flags); const bool buf_dirty = callback_data.BufDirty; - if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty) { state->Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); state->CursorFollow = true; } - if (callback_data.SelectionStart != utf8_selection_start || buf_dirty) { state->Stb.select_start = (callback_data.SelectionStart == callback_data.CursorPos) ? state->Stb.cursor : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); } - if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty) { state->Stb.select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? state->Stb.select_start : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); } + if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty) { state->Stb->cursor = callback_data.CursorPos; state->CursorFollow = true; } + if (callback_data.SelectionStart != utf8_selection_start || buf_dirty) { state->Stb->select_start = (callback_data.SelectionStart == callback_data.CursorPos) ? state->Stb->cursor : callback_data.SelectionStart; } + if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty) { state->Stb->select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? state->Stb->select_start : callback_data.SelectionEnd; } if (buf_dirty) { - IM_ASSERT(!is_readonly); + // Callback may update buffer and thus set buf_dirty even in read-only mode. IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! InputTextReconcileUndoStateAfterUserCallback(state, callback_data.Buf, callback_data.BufTextLen); // FIXME: Move the rest of this block inside function and rename to InputTextReconcileStateAfterUserCallback() ? - if (callback_data.BufTextLen > backup_current_text_length && is_resizable) - state->TextW.resize(state->TextW.Size + (callback_data.BufTextLen - backup_current_text_length)); // Worse case scenario resize - state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, callback_data.Buf, NULL); - state->CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() + state->TextLen = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() state->CursorAnimReset(); } } @@ -4810,7 +5056,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (!is_readonly && strcmp(state->TextA.Data, buf) != 0) { apply_new_text = state->TextA.Data; - apply_new_text_length = state->CurLenA; + apply_new_text_length = state->TextLen; value_changed = true; } } @@ -4832,9 +5078,9 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Copy result to user buffer. This can currently only happen when (g.ActiveId == id) if (apply_new_text != NULL) { - // We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size - // of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used - // without any storage on user's side. + //// We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size + //// of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used + //// without any storage on user's side. IM_ASSERT(apply_new_text_length >= 0); if (is_resizable) { @@ -4868,7 +5114,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Render frame if (!is_multiline) { - RenderNavHighlight(frame_bb, id); + RenderNavCursor(frame_bb, id); RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); } @@ -4894,7 +5140,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { IM_ASSERT(state != NULL); if (!is_displaying_hint) - buf_display_end = buf_display + state->CurLenA; + buf_display_end = buf_display + state->TextLen; // Render text (with cursor and selection) // This is going to be messy. We need to: @@ -4903,52 +5149,40 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // - Measure text height (for scrollbar) // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort) // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8. - const ImWchar* text_begin = state->TextW.Data; + const char* text_begin = state->TextA.Data; + const char* text_end = text_begin + state->TextLen; ImVec2 cursor_offset, select_start_offset; { - // Find lines numbers straddling 'cursor' (slot 0) and 'select_start' (slot 1) positions. - const ImWchar* searches_input_ptr[2] = { NULL, NULL }; - int searches_result_line_no[2] = { -1000, -1000 }; - int searches_remaining = 0; - if (render_cursor) - { - searches_input_ptr[0] = text_begin + state->Stb.cursor; - searches_result_line_no[0] = -1; - searches_remaining++; - } - if (render_selection) - { - searches_input_ptr[1] = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end); - searches_result_line_no[1] = -1; - searches_remaining++; - } + // Find lines numbers straddling cursor and selection min position + int cursor_line_no = render_cursor ? -1 : -1000; + int selmin_line_no = render_selection ? -1 : -1000; + const char* cursor_ptr = render_cursor ? text_begin + state->Stb->cursor : NULL; + const char* selmin_ptr = render_selection ? text_begin + ImMin(state->Stb->select_start, state->Stb->select_end) : NULL; - // Iterate all lines to find our line numbers - // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter. - searches_remaining += is_multiline ? 1 : 0; - int line_count = 0; - //for (const ImWchar* s = text_begin; (s = (const ImWchar*)wcschr((const wchar_t*)s, (wchar_t)'\n')) != NULL; s++) // FIXME-OPT: Could use this when wchar_t are 16-bit - for (const ImWchar* s = text_begin; *s != 0; s++) - if (*s == '\n') + // Count lines and find line number for cursor and selection ends + int line_count = 1; + if (is_multiline) + { + for (const char* s = text_begin; (s = (const char*)memchr(s, '\n', (size_t)(text_end - s))) != NULL; s++) { + if (cursor_line_no == -1 && s >= cursor_ptr) { cursor_line_no = line_count; } + if (selmin_line_no == -1 && s >= selmin_ptr) { selmin_line_no = line_count; } line_count++; - if (searches_result_line_no[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_no[0] = line_count; if (--searches_remaining <= 0) break; } - if (searches_result_line_no[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_no[1] = line_count; if (--searches_remaining <= 0) break; } } - line_count++; - if (searches_result_line_no[0] == -1) - searches_result_line_no[0] = line_count; - if (searches_result_line_no[1] == -1) - searches_result_line_no[1] = line_count; + } + if (cursor_line_no == -1) + cursor_line_no = line_count; + if (selmin_line_no == -1) + selmin_line_no = line_count; // Calculate 2d position by finding the beginning of the line and measuring distance - cursor_offset.x = InputTextCalcTextSizeW(&g, ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x; - cursor_offset.y = searches_result_line_no[0] * g.FontSize; - if (searches_result_line_no[1] >= 0) + cursor_offset.x = InputTextCalcTextSize(&g, ImStrbol(cursor_ptr, text_begin), cursor_ptr).x; + cursor_offset.y = cursor_line_no * g.FontSize; + if (selmin_line_no >= 0) { - select_start_offset.x = InputTextCalcTextSizeW(&g, ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x; - select_start_offset.y = searches_result_line_no[1] * g.FontSize; + select_start_offset.x = InputTextCalcTextSize(&g, ImStrbol(selmin_ptr, text_begin), selmin_ptr).x; + select_start_offset.y = selmin_line_no * g.FontSize; } // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224) @@ -4964,14 +5198,14 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { const float scroll_increment_x = inner_size.x * 0.25f; const float visible_width = inner_size.x - style.FramePadding.x; - if (cursor_offset.x < state->ScrollX) - state->ScrollX = IM_TRUNC(ImMax(0.0f, cursor_offset.x - scroll_increment_x)); - else if (cursor_offset.x - visible_width >= state->ScrollX) - state->ScrollX = IM_TRUNC(cursor_offset.x - visible_width + scroll_increment_x); + if (cursor_offset.x < state->Scroll.x) + state->Scroll.x = IM_TRUNC(ImMax(0.0f, cursor_offset.x - scroll_increment_x)); + else if (cursor_offset.x - visible_width >= state->Scroll.x) + state->Scroll.x = IM_TRUNC(cursor_offset.x - visible_width + scroll_increment_x); } else { - state->ScrollX = 0.0f; + state->Scroll.y = 0.0f; } // Vertical scroll @@ -4992,43 +5226,41 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } // Draw selection - const ImVec2 draw_scroll = ImVec2(state->ScrollX, 0.0f); + const ImVec2 draw_scroll = ImVec2(state->Scroll.x, 0.0f); if (render_selection) { - const ImWchar* text_selected_begin = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end); - const ImWchar* text_selected_end = text_begin + ImMax(state->Stb.select_start, state->Stb.select_end); + const char* text_selected_begin = text_begin + ImMin(state->Stb->select_start, state->Stb->select_end); + const char* text_selected_end = text_begin + ImMax(state->Stb->select_start, state->Stb->select_end); ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests. float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. float bg_offy_dn = is_multiline ? 0.0f : 2.0f; ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll; - for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) + for (const char* p = text_selected_begin; p < text_selected_end; ) { if (rect_pos.y > clip_rect.w + g.FontSize) break; if (rect_pos.y < clip_rect.y) { - //p = (const ImWchar*)wmemchr((const wchar_t*)p, '\n', text_selected_end - p); // FIXME-OPT: Could use this when wchar_t are 16-bit - //p = p ? p + 1 : text_selected_end; - while (p < text_selected_end) - if (*p++ == '\n') - break; + p = (const char*)memchr((void*)p, '\n', text_selected_end - p); + p = p ? p + 1 : text_selected_end; } else { - ImVec2 rect_size = InputTextCalcTextSizeW(&g, p, text_selected_end, &p, NULL, true); + ImVec2 rect_size = InputTextCalcTextSize(&g, p, text_selected_end, &p, NULL, true); if (rect_size.x <= 0.0f) rect_size.x = IM_TRUNC(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn)); rect.ClipWith(clip_rect); if (rect.Overlaps(clip_rect)) draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); + rect_pos.x = draw_pos.x - draw_scroll.x; } - rect_pos.x = draw_pos.x - draw_scroll.x; rect_pos.y += g.FontSize; } } // We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash. + // FIXME-OPT: Multiline could submit a smaller amount of contents to AddText() since we already iterated through it. if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) { ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); @@ -5051,6 +5283,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ g.PlatformImeData.WantVisible = true; g.PlatformImeData.InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize); g.PlatformImeData.InputLineHeight = g.FontSize; + g.PlatformImeViewport = window->Viewport->ID; } } } @@ -5060,14 +5293,19 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_multiline) text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_display_end) * g.FontSize); // We don't need width else if (!is_displaying_hint && g.ActiveId == id) - buf_display_end = buf_display + state->CurLenA; + buf_display_end = buf_display + state->TextLen; else if (!is_displaying_hint) buf_display_end = buf_display + strlen(buf_display); if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) { + // Find render position for right alignment + if (flags & ImGuiInputTextFlags_ElideLeft) + draw_pos.x = ImMin(draw_pos.x, frame_bb.Max.x - CalcTextSize(buf_display, NULL).x - style.FramePadding.x); + + const ImVec2 draw_scroll = /*state ? ImVec2(state->Scroll.x, 0.0f) :*/ ImVec2(0.0f, 0.0f); // Preserve scroll when inactive? ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); - draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect); + draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect); } } @@ -5076,19 +5314,19 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_multiline) { - // For focus requests to work on our multiline we need to ensure our child ItemAdd() call specifies the ImGuiItemFlags_Inputable (ref issue #4761)... + // For focus requests to work on our multiline we need to ensure our child ItemAdd() call specifies the ImGuiItemFlags_Inputable (see #4761, #7870)... Dummy(ImVec2(text_size.x, text_size.y + style.FramePadding.y)); - g.NextItemData.ItemFlags |= ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop; + g.NextItemData.ItemFlags |= (ImGuiItemFlags)ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop; EndChild(); item_data_backup.StatusFlags |= (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredWindow); // ...and then we need to undo the group overriding last item data, which gets a bit messy as EndGroup() tries to forward scrollbar being active... // FIXME: This quite messy/tricky, should attempt to get rid of the child window. EndGroup(); - if (g.LastItemData.ID == 0) + if (g.LastItemData.ID == 0 || g.LastItemData.ID != GetWindowScrollbarID(draw_window, ImGuiAxis_Y)) { g.LastItemData.ID = id; - g.LastItemData.InFlags = item_data_backup.InFlags; + g.LastItemData.ItemFlags = item_data_backup.ItemFlags; g.LastItemData.StatusFlags = item_data_backup.StatusFlags; } } @@ -5103,7 +5341,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (label_size.x > 0) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); - if (value_changed && !(flags & ImGuiInputTextFlags_NoMarkEdited)) + if (value_changed) MarkItemEdited(id); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable); @@ -5117,14 +5355,16 @@ void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state) { #ifndef IMGUI_DISABLE_DEBUG_TOOLS ImGuiContext& g = *GImGui; - ImStb::STB_TexteditState* stb_state = &state->Stb; + ImStb::STB_TexteditState* stb_state = state->Stb; ImStb::StbUndoState* undo_state = &stb_state->undostate; Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId); DebugLocateItemOnHover(state->ID); - Text("CurLenW: %d, CurLenA: %d, Cursor: %d, Selection: %d..%d", state->CurLenW, state->CurLenA, stb_state->cursor, stb_state->select_start, stb_state->select_end); + Text("CurLenA: %d, Cursor: %d, Selection: %d..%d", state->TextLen, stb_state->cursor, stb_state->select_start, stb_state->select_end); + Text("BufCapacityA: %d", state->BufCapacity); + Text("(Internal Buffer: TextA Size: %d, Capacity: %d)", state->TextA.Size, state->TextA.Capacity); Text("has_preferred_x: %d (%.2f)", stb_state->has_preferred_x, stb_state->preferred_x); Text("undo_point: %d, redo_point: %d, undo_char_point: %d, redo_char_point: %d", undo_state->undo_point, undo_state->redo_point, undo_state->undo_char_point, undo_state->redo_char_point); - if (BeginChild("undopoints", ImVec2(0.0f, GetTextLineHeight() * 10), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeY)) // Visualize undo state + if (BeginChild("undopoints", ImVec2(0.0f, GetTextLineHeight() * 10), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY)) // Visualize undo state { PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); for (int n = 0; n < IMSTB_TEXTEDIT_UNDOSTATECOUNT; n++) @@ -5133,11 +5373,10 @@ void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state) const char undo_rec_type = (n < undo_state->undo_point) ? 'u' : (n >= undo_state->redo_point) ? 'r' : ' '; if (undo_rec_type == ' ') BeginDisabled(); - char buf[64] = ""; - if (undo_rec_type != ' ' && undo_rec->char_storage != -1) - ImTextStrToUtf8(buf, IM_ARRAYSIZE(buf), undo_state->undo_char + undo_rec->char_storage, undo_state->undo_char + undo_rec->char_storage + undo_rec->insert_length); - Text("%c [%02d] where %03d, insert %03d, delete %03d, char_storage %03d \"%s\"", - undo_rec_type, n, undo_rec->where, undo_rec->insert_length, undo_rec->delete_length, undo_rec->char_storage, buf); + const int buf_preview_len = (undo_rec_type != ' ' && undo_rec->char_storage != -1) ? undo_rec->insert_length : 0; + const char* buf_preview_str = undo_state->undo_char + undo_rec->char_storage; + Text("%c [%02d] where %03d, insert %03d, delete %03d, char_storage %03d \"%.*s\"", + undo_rec_type, n, undo_rec->where, undo_rec->insert_length, undo_rec->delete_length, undo_rec->char_storage, buf_preview_len, buf_preview_str); if (undo_rec_type == ' ') EndDisabled(); } @@ -5418,7 +5657,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag // Drag and Drop Target // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test. - if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget()) + if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget()) { bool accepted_drag_drop = false; if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) @@ -5894,7 +6133,7 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl else window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding); } - RenderNavHighlight(bb, id); + RenderNavCursor(bb, id); if ((flags & ImGuiColorEditFlags_NoBorder) == 0) { if (g.Style.FrameBorderSize > 0.0f) @@ -5985,8 +6224,9 @@ void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) bool allow_opt_datatype = !(flags & ImGuiColorEditFlags_DataTypeMask_); if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context")) return; + ImGuiContext& g = *GImGui; - g.LockMarkEdited++; + PushItemFlag(ImGuiItemFlags_NoMarkEdited, true); ImGuiColorEditFlags opts = g.ColorEditOptions; if (allow_opt_inputs) { @@ -6028,8 +6268,8 @@ void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) } g.ColorEditOptions = opts; + PopItemFlag(); EndPopup(); - g.LockMarkEdited--; } void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags) @@ -6038,8 +6278,9 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar); if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context")) return; + ImGuiContext& g = *GImGui; - g.LockMarkEdited++; + PushItemFlag(ImGuiItemFlags_NoMarkEdited, true); if (allow_opt_picker) { ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function @@ -6068,8 +6309,8 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl if (allow_opt_picker) Separator(); CheckboxFlags("Alpha Bar", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar); } + PopItemFlag(); EndPopup(); - g.LockMarkEdited--; } //------------------------------------------------------------------------- @@ -6110,7 +6351,8 @@ bool ImGui::TreeNode(const char* label) ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; - return TreeNodeBehavior(window->GetID(label), 0, label, NULL); + ImGuiID id = window->GetID(label); + return TreeNodeBehavior(id, ImGuiTreeNodeFlags_None, label, NULL); } bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args) @@ -6128,8 +6370,8 @@ bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; - - return TreeNodeBehavior(window->GetID(label), flags, label, NULL); + ImGuiID id = window->GetID(label); + return TreeNodeBehavior(id, flags, label, NULL); } bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) @@ -6156,9 +6398,10 @@ bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char if (window->SkipItems) return false; + ImGuiID id = window->GetID(str_id); const char* label, *label_end; ImFormatStringToTempBufferV(&label, &label_end, fmt, args); - return TreeNodeBehavior(window->GetID(str_id), flags, label, label_end); + return TreeNodeBehavior(id, flags, label, label_end); } bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) @@ -6167,44 +6410,52 @@ bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char if (window->SkipItems) return false; + ImGuiID id = window->GetID(ptr_id); const char* label, *label_end; ImFormatStringToTempBufferV(&label, &label_end, fmt, args); - return TreeNodeBehavior(window->GetID(ptr_id), flags, label, label_end); + return TreeNodeBehavior(id, flags, label, label_end); +} + +bool ImGui::TreeNodeGetOpen(ImGuiID storage_id) +{ + ImGuiContext& g = *GImGui; + ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage; + return storage->GetInt(storage_id, 0) != 0; } -void ImGui::TreeNodeSetOpen(ImGuiID id, bool open) +void ImGui::TreeNodeSetOpen(ImGuiID storage_id, bool open) { ImGuiContext& g = *GImGui; ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage; - storage->SetInt(id, open ? 1 : 0); + storage->SetInt(storage_id, open ? 1 : 0); } -bool ImGui::TreeNodeUpdateNextOpen(ImGuiID id, ImGuiTreeNodeFlags flags) +bool ImGui::TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags) { if (flags & ImGuiTreeNodeFlags_Leaf) return true; - // We only write to the tree storage if the user clicks (or explicitly use the SetNextItemOpen function) + // We only write to the tree storage if the user clicks, or explicitly use the SetNextItemOpen function ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImGuiStorage* storage = window->DC.StateStorage; bool is_open; - if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasOpen) + if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasOpen) { if (g.NextItemData.OpenCond & ImGuiCond_Always) { is_open = g.NextItemData.OpenVal; - TreeNodeSetOpen(id, is_open); + TreeNodeSetOpen(storage_id, is_open); } else { // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently. - const int stored_value = storage->GetInt(id, -1); + const int stored_value = storage->GetInt(storage_id, -1); if (stored_value == -1) { is_open = g.NextItemData.OpenVal; - TreeNodeSetOpen(id, is_open); + TreeNodeSetOpen(storage_id, is_open); } else { @@ -6214,7 +6465,7 @@ bool ImGui::TreeNodeUpdateNextOpen(ImGuiID id, ImGuiTreeNodeFlags flags) } else { - is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0; + is_open = storage->GetInt(storage_id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0; } // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior). @@ -6225,6 +6476,23 @@ bool ImGui::TreeNodeUpdateNextOpen(ImGuiID id, ImGuiTreeNodeFlags flags) return is_open; } +// Store ImGuiTreeNodeStackData for just submitted node. +// Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase. +static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + g.TreeNodeStack.resize(g.TreeNodeStack.Size + 1); + ImGuiTreeNodeStackData* tree_node_data = &g.TreeNodeStack.back(); + tree_node_data->ID = g.LastItemData.ID; + tree_node_data->TreeFlags = flags; + tree_node_data->ItemFlags = g.LastItemData.ItemFlags; + tree_node_data->NavRect = g.LastItemData.NavRect; + window->DC.TreeHasStackDataDepthMask |= (1 << window->DC.TreeDepth); +} + +// When using public API, currently 'id == storage_id' is always true, but we separate the values to facilitate advanced user code doing storage queries outside of UI loop. bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) { ImGuiWindow* window = GetCurrentWindow(); @@ -6254,10 +6522,9 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l frame_bb.Max.y = window->DC.CursorPos.y + frame_height; if (display_frame) { - // Framed header expand a little outside the default padding, to the edge of InnerClipRect - // (FIXME: May remove this at some point and make InnerClipRect align with WindowPadding.x instead of WindowPadding.x*0.5f) - frame_bb.Min.x -= IM_TRUNC(window->WindowPadding.x * 0.5f - 1.0f); - frame_bb.Max.x += IM_TRUNC(window->WindowPadding.x * 0.5f); + const float outer_extend = IM_TRUNC(window->WindowPadding.x * 0.5f); // Framed header expand a little outside of current limits + frame_bb.Min.x -= outer_extend; + frame_bb.Max.x += outer_extend; } ImVec2 text_pos(window->DC.CursorPos.x + text_offset_x, window->DC.CursorPos.y + text_offset_y); @@ -6268,46 +6535,46 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l if ((flags & (ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanTextWidth | ImGuiTreeNodeFlags_SpanAllColumns)) == 0) interact_bb.Max.x = frame_bb.Min.x + text_width + (label_size.x > 0.0f ? style.ItemSpacing.x * 2.0f : 0.0f); - // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable.. - const float backup_clip_rect_min_x = window->ClipRect.Min.x; - const float backup_clip_rect_max_x = window->ClipRect.Max.x; - if (span_all_columns) - { - window->ClipRect.Min.x = window->ParentWorkRect.Min.x; - window->ClipRect.Max.x = window->ParentWorkRect.Max.x; - } - // Compute open and multi-select states before ItemAdd() as it clear NextItem data. - bool is_open = TreeNodeUpdateNextOpen(id, flags); - bool item_add = ItemAdd(interact_bb, id); - g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; - g.LastItemData.DisplayRect = frame_bb; + ImGuiID storage_id = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasStorageID) ? g.NextItemData.StorageId : id; + bool is_open = TreeNodeUpdateNextOpen(storage_id, flags); + bool is_visible; if (span_all_columns) { + // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable.. + const float backup_clip_rect_min_x = window->ClipRect.Min.x; + const float backup_clip_rect_max_x = window->ClipRect.Max.x; + window->ClipRect.Min.x = window->ParentWorkRect.Min.x; + window->ClipRect.Max.x = window->ParentWorkRect.Max.x; + is_visible = ItemAdd(interact_bb, id); window->ClipRect.Min.x = backup_clip_rect_min_x; window->ClipRect.Max.x = backup_clip_rect_max_x; } + else + { + is_visible = ItemAdd(interact_bb, id); + } + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; + g.LastItemData.DisplayRect = frame_bb; // If a NavLeft request is happening and ImGuiTreeNodeFlags_NavLeftJumpsBackHere enabled: // Store data for the current depth to allow returning to this node from any child item. // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop(). // It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsBackHere by default or move it to ImGuiStyle. - // Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase. - if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) - if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) - { - g.NavTreeNodeStack.resize(g.NavTreeNodeStack.Size + 1); - ImGuiNavTreeNodeData* nav_tree_node_data = &g.NavTreeNodeStack.back(); - nav_tree_node_data->ID = id; - nav_tree_node_data->InFlags = g.LastItemData.InFlags; - nav_tree_node_data->NavRect = g.LastItemData.NavRect; - window->DC.TreeJumpToParentOnPopMask |= (1 << window->DC.TreeDepth); - } + bool store_tree_node_stack_data = false; + if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + { + if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && is_open && !g.NavIdIsAlive) + if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) + store_tree_node_stack_data = true; + } const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0; - if (!item_add) + if (!is_visible) { + if (store_tree_node_stack_data && is_open) + TreeNodeStoreStackData(flags); // Call before TreePushOverrideID() if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) TreePushOverrideID(id); IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); @@ -6322,7 +6589,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l } ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None; - if ((flags & ImGuiTreeNodeFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap)) + if ((flags & ImGuiTreeNodeFlags_AllowOverlap) || (g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap)) button_flags |= ImGuiButtonFlags_AllowOverlap; if (!is_leaf) button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; @@ -6333,8 +6600,10 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l const float arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x; const float arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x * 2.0f) + style.TouchExtraPadding.x; const bool is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2); - if (window != g.HoveredWindow || !is_mouse_x_over_arrow) - button_flags |= ImGuiButtonFlags_NoKeyModifiers; + + const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0; + if (is_multi_select) // We absolutely need to distinguish open vs select so _OpenOnArrow comes by default + flags |= (flags & ImGuiTreeNodeFlags_OpenOnMask_) == 0 ? ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick : ImGuiTreeNodeFlags_OpenOnArrow; // Open behaviors can be altered with the _OpenOnArrow and _OnOnDoubleClick flags. // Some alteration have subtle effects (e.g. toggle on MouseUp vs MouseDown events) due to requirements for multi-selection and drag and drop support. @@ -6355,6 +6624,20 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0; const bool was_selected = selected; + // Multi-selection support (header) + if (is_multi_select) + { + // Handle multi-select + alter button flags for it + MultiSelectItemHeader(id, &selected, &button_flags); + if (is_mouse_x_over_arrow) + button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease; + } + else + { + if (window != g.HoveredWindow || !is_mouse_x_over_arrow) + button_flags |= ImGuiButtonFlags_NoKeyModsAllowed; + } + bool hovered, held; bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags); bool toggled = false; @@ -6362,18 +6645,20 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l { if (pressed && g.DragDropHoldJustPressedId != id) { - if ((flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) == 0 || (g.NavActivateId == id)) - toggled = true; + if ((flags & ImGuiTreeNodeFlags_OpenOnMask_) == 0 || (g.NavActivateId == id && !is_multi_select)) + toggled = true; // Single click if (flags & ImGuiTreeNodeFlags_OpenOnArrow) - toggled |= is_mouse_x_over_arrow && !g.NavDisableMouseHover; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job + toggled |= is_mouse_x_over_arrow && !g.NavHighlightItemUnderNav; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseClickedCount[0] == 2) - toggled = true; + toggled = true; // Double click } else if (pressed && g.DragDropHoldJustPressedId == id) { IM_ASSERT(button_flags & ImGuiButtonFlags_PressedOnDragDropHold); if (!is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again. toggled = true; + else + pressed = false; // Cancel press so it doesn't trigger selection. } if (g.NavId == id && g.NavMoveDir == ImGuiDir_Left && is_open) @@ -6392,64 +6677,78 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l if (toggled) { is_open = !is_open; - window->DC.StateStorage->SetInt(id, is_open); + window->DC.StateStorage->SetInt(storage_id, is_open); g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledOpen; } } - // In this branch, TreeNodeBehavior() cannot toggle the selection so this will never trigger. - if (selected != was_selected) //-V547 + // Multi-selection support (footer) + if (is_multi_select) + { + bool pressed_copy = pressed && !toggled; + MultiSelectItemFooter(id, &selected, &pressed_copy); + if (pressed) + SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, interact_bb); + } + + if (selected != was_selected) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection; // Render - const ImU32 text_col = GetColorU32(ImGuiCol_Text); - ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_Compact; - if (display_frame) - { - // Framed type - const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); - RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding); - RenderNavHighlight(frame_bb, id, nav_highlight_flags); - if (flags & ImGuiTreeNodeFlags_Bullet) - RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col); - else if (!is_leaf) - RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y), text_col, is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, 1.0f); - else // Leaf without bullet, left-adjusted text - text_pos.x -= text_offset_x -padding.x; - if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton) - frame_bb.Max.x -= g.FontSize + style.FramePadding.x; - - if (g.LogEnabled) - LogSetNextTextDecoration("###", "###"); - } - else { - // Unframed typed for tree nodes - if (hovered || selected) + const ImU32 text_col = GetColorU32(ImGuiCol_Text); + ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact; + if (is_multi_select) + nav_render_cursor_flags |= ImGuiNavRenderCursorFlags_AlwaysDraw; // Always show the nav rectangle + if (display_frame) { + // Framed type const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); - RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false); + RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding); + RenderNavCursor(frame_bb, id, nav_render_cursor_flags); + if (flags & ImGuiTreeNodeFlags_Bullet) + RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col); + else if (!is_leaf) + RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y), text_col, is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, 1.0f); + else // Leaf without bullet, left-adjusted text + text_pos.x -= text_offset_x - padding.x; + if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton) + frame_bb.Max.x -= g.FontSize + style.FramePadding.x; + if (g.LogEnabled) + LogSetNextTextDecoration("###", "###"); } - RenderNavHighlight(frame_bb, id, nav_highlight_flags); - if (flags & ImGuiTreeNodeFlags_Bullet) - RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col); - else if (!is_leaf) - RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.15f), text_col, is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, 0.70f); - if (g.LogEnabled) - LogSetNextTextDecoration(">", NULL); - } - - if (span_all_columns) - TablePopBackgroundChannel(); - - // Label - if (display_frame) - RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); - else - RenderText(text_pos, label, label_end, false); + else + { + // Unframed typed for tree nodes + if (hovered || selected) + { + const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false); + } + RenderNavCursor(frame_bb, id, nav_render_cursor_flags); + if (flags & ImGuiTreeNodeFlags_Bullet) + RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col); + else if (!is_leaf) + RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.15f), text_col, is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, 0.70f); + if (g.LogEnabled) + LogSetNextTextDecoration(">", NULL); + } + + if (span_all_columns) + TablePopBackgroundChannel(); + // Label + if (display_frame) + RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); + else + RenderText(text_pos, label, label_end, false); + } + + if (store_tree_node_stack_data && is_open) + TreeNodeStoreStackData(flags); // Call before TreePushOverrideID() if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) - TreePushOverrideID(id); + TreePushOverrideID(id); // Could use TreePush(label) but this avoid computing twice + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); return is_open; } @@ -6488,16 +6787,19 @@ void ImGui::TreePop() window->DC.TreeDepth--; ImU32 tree_depth_mask = (1 << window->DC.TreeDepth); - // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled) - if (window->DC.TreeJumpToParentOnPopMask & tree_depth_mask) // Only set during request + if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask) // Only set during request { - ImGuiNavTreeNodeData* nav_tree_node_data = &g.NavTreeNodeStack.back(); - IM_ASSERT(nav_tree_node_data->ID == window->IDStack.back()); - if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) - NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, nav_tree_node_data); - g.NavTreeNodeStack.pop_back(); + ImGuiTreeNodeStackData* data = &g.TreeNodeStack.back(); + IM_ASSERT(data->ID == window->IDStack.back()); + if (data->TreeFlags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) + { + // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled) + if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) + NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, data); + } + g.TreeNodeStack.pop_back(); + window->DC.TreeHasStackDataDepthMask &= ~tree_depth_mask; } - window->DC.TreeJumpToParentOnPopMask &= tree_depth_mask - 1; IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much. PopID(); @@ -6516,11 +6818,21 @@ void ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond) ImGuiContext& g = *GImGui; if (g.CurrentWindow->SkipItems) return; - g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasOpen; + g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasOpen; g.NextItemData.OpenVal = is_open; g.NextItemData.OpenCond = (ImU8)(cond ? cond : ImGuiCond_Always); } +// Set next TreeNode/CollapsingHeader storage id. +void ImGui::SetNextItemStorageID(ImGuiID storage_id) +{ + ImGuiContext& g = *GImGui; + if (g.CurrentWindow->SkipItems) + return; + g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasStorageID; + g.NextItemData.StorageId = storage_id; +} + // CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag). // This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode(). bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) @@ -6528,8 +6840,8 @@ bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; - - return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader, label); + ImGuiID id = window->GetID(label); + return TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader, label); } // p_visible == NULL : regular collapsing header @@ -6609,6 +6921,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl const ImVec2 text_max(min_x + size.x, pos.y + size.y); // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable. + // FIXME: Not part of layout so not included in clipper calculation, but ItemSize currently doesn't allow offsetting CursorPos. ImRect bb(min_x, pos.y, text_max.x, text_max.y); if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0) { @@ -6623,25 +6936,29 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl } //if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); } - // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable.. - const float backup_clip_rect_min_x = window->ClipRect.Min.x; - const float backup_clip_rect_max_x = window->ClipRect.Max.x; + const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0; + const ImGuiItemFlags extra_item_flags = disabled_item ? (ImGuiItemFlags)ImGuiItemFlags_Disabled : ImGuiItemFlags_None; + bool is_visible; if (span_all_columns) { + // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable.. + const float backup_clip_rect_min_x = window->ClipRect.Min.x; + const float backup_clip_rect_max_x = window->ClipRect.Max.x; window->ClipRect.Min.x = window->ParentWorkRect.Min.x; window->ClipRect.Max.x = window->ParentWorkRect.Max.x; - } - - const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0; - const bool item_add = ItemAdd(bb, id, NULL, disabled_item ? ImGuiItemFlags_Disabled : ImGuiItemFlags_None); - if (span_all_columns) - { + is_visible = ItemAdd(bb, id, NULL, extra_item_flags); window->ClipRect.Min.x = backup_clip_rect_min_x; window->ClipRect.Max.x = backup_clip_rect_max_x; } + else + { + is_visible = ItemAdd(bb, id, NULL, extra_item_flags); + } - if (!item_add) - return false; + const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0; + if (!is_visible) + if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(bb)) // Extra layer of "no logic clip" for box-select support (would be more overhead to add to ItemAdd) + return false; const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; if (disabled_item && !disabled_global) // Only testing this as an optimization @@ -6666,47 +6983,72 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (flags & ImGuiSelectableFlags_SelectOnClick) { button_flags |= ImGuiButtonFlags_PressedOnClick; } if (flags & ImGuiSelectableFlags_SelectOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; } if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; } - if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; } + if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; } + // Multi-selection support (header) const bool was_selected = selected; + if (is_multi_select) + { + // Handle multi-select + alter button flags for it + MultiSelectItemHeader(id, &selected, &button_flags); + } + bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); - // Auto-select when moved into - // - This will be more fully fleshed in the range-select branch - // - This is not exposed as it won't nicely work with some user side handling of shift/control - // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons - // - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope()) - // - (2) usage will fail with clipped items - // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API. - if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId) - if (g.NavJustMovedToId == id) - selected = pressed = true; - - // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard + // Multi-selection support (footer) + if (is_multi_select) + { + MultiSelectItemFooter(id, &selected, &pressed); + } + else + { + // Auto-select when moved into + // - This will be more fully fleshed in the range-select branch + // - This is not exposed as it won't nicely work with some user side handling of shift/control + // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons + // - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope()) + // - (2) usage will fail with clipped items + // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API. + if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId) + if (g.NavJustMovedToId == id) + selected = pressed = true; + } + + // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with keyboard/gamepad if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover))) { - if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) + if (!g.NavHighlightItemUnderNav && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) { SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, WindowRectAbsToRel(window, bb)); // (bb == NavRect) - g.NavDisableHighlight = true; + if (g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = false; } } if (pressed) MarkItemEdited(id); - // In this branch, Selectable() cannot toggle the selection so this will never trigger. - if (selected != was_selected) //-V547 + if (selected != was_selected) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection; // Render - if (hovered || selected) + if (is_visible) { - const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); - RenderFrame(bb.Min, bb.Max, col, false, 0.0f); + const bool highlighted = hovered || (flags & ImGuiSelectableFlags_Highlight); + if (highlighted || selected) + { + // Between 1.91.0 and 1.91.4 we made selected Selectable use an arbitrary lerp between _Header and _HeaderHovered. Removed that now. (#8106) + ImU32 col = GetColorU32((held && highlighted) ? ImGuiCol_HeaderActive : highlighted ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(bb.Min, bb.Max, col, false, 0.0f); + } + if (g.NavId == id) + { + ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact | ImGuiNavRenderCursorFlags_NoRounding; + if (is_multi_select) + nav_render_cursor_flags |= ImGuiNavRenderCursorFlags_AlwaysDraw; // Always show the nav rectangle + RenderNavCursor(bb, id, nav_render_cursor_flags); + } } - if (g.NavId == id) - RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_Compact | ImGuiNavHighlightFlags_NoRounding); if (span_all_columns) { @@ -6716,15 +7058,19 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl PopColumnsBackground(); } - RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb); + if (is_visible) + RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb); // Automatically close popups - if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(g.LastItemData.InFlags & ImGuiItemFlags_SelectableDontClosePopup)) + if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.ItemFlags & ImGuiItemFlags_AutoClosePopups)) CloseCurrentPopup(); if (disabled_item && !disabled_global) EndDisabled(); + // Selectable() always returns a pressed state! + // Users of BeginMultiSelect()/EndMultiSelect() scope: you may call ImGui::IsItemToggledSelection() to retrieve + // selection toggle, only useful if you need that state updated (e.g. for rendering purpose) before reaching EndMultiSelect(). IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return pressed; //-V1020 } @@ -6860,7 +7206,7 @@ static int ImStrimatchlen(const char* s1, const char* s1_end, const char* s2) // When SingleCharMode is set: // - it is better to NOT display a tooltip of other on-screen display indicator. // - the index of the currently focused item is required. -// if your SetNextItemSelectionData() values are indices, you can obtain it from ImGuiMultiSelectIO::NavIdItem, otherwise from g.NavLastValidSelectionUserData. +// if your SetNextItemSelectionUserData() values are indices, you can obtain it from ImGuiMultiSelectIO::NavIdItem, otherwise from g.NavLastValidSelectionUserData. int ImGui::TypingSelectFindMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx) { if (req == NULL || req->SelectRequest == false) // Support NULL parameter so both calls can be done from same spot. @@ -6871,7 +7217,7 @@ int ImGui::TypingSelectFindMatch(ImGuiTypingSelectRequest* req, int items_count, else idx = TypingSelectFindBestLeadingMatch(req, items_count, get_item_name_func, user_data); if (idx != -1) - NavRestoreHighlightAfterMove(); + SetNavCursorVisibleAfterMove(); return idx; } @@ -6930,20 +7276,933 @@ void ImGui::DebugNodeTypingSelectState(ImGuiTypingSelectState* data) #endif } +//------------------------------------------------------------------------- +// [SECTION] Widgets: Box-Select support +// This has been extracted away from Multi-Select logic in the hope that it could eventually be used elsewhere, but hasn't been yet. +//------------------------------------------------------------------------- +// Extra logic in MultiSelectItemFooter() and ImGuiListClipper::Step() +//------------------------------------------------------------------------- +// - BoxSelectPreStartDrag() [Internal] +// - BoxSelectActivateDrag() [Internal] +// - BoxSelectDeactivateDrag() [Internal] +// - BoxSelectScrollWithMouseDrag() [Internal] +// - BeginBoxSelect() [Internal] +// - EndBoxSelect() [Internal] +//------------------------------------------------------------------------- + +// Call on the initial click. +static void BoxSelectPreStartDrag(ImGuiID id, ImGuiSelectionUserData clicked_item) +{ + ImGuiContext& g = *GImGui; + ImGuiBoxSelectState* bs = &g.BoxSelectState; + bs->ID = id; + bs->IsStarting = true; // Consider starting box-select. + bs->IsStartedFromVoid = (clicked_item == ImGuiSelectionUserData_Invalid); + bs->IsStartedSetNavIdOnce = bs->IsStartedFromVoid; + bs->KeyMods = g.IO.KeyMods; + bs->StartPosRel = bs->EndPosRel = ImGui::WindowPosAbsToRel(g.CurrentWindow, g.IO.MousePos); + bs->ScrollAccum = ImVec2(0.0f, 0.0f); +} + +static void BoxSelectActivateDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Activate\n", bs->ID); + bs->IsActive = true; + bs->Window = window; + bs->IsStarting = false; + ImGui::SetActiveID(bs->ID, window); + ImGui::SetActiveIdUsingAllKeyboardKeys(); + if (bs->IsStartedFromVoid && (bs->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0) + bs->RequestClear = true; +} + +static void BoxSelectDeactivateDrag(ImGuiBoxSelectState* bs) +{ + ImGuiContext& g = *GImGui; + bs->IsActive = bs->IsStarting = false; + if (g.ActiveId == bs->ID) + { + IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Deactivate\n", bs->ID); + ImGui::ClearActiveID(); + } + bs->ID = 0; +} + +static void BoxSelectScrollWithMouseDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window, const ImRect& inner_r) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(bs->Window == window); + for (int n = 0; n < 2; n++) // each axis + { + const float mouse_pos = g.IO.MousePos[n]; + const float dist = (mouse_pos > inner_r.Max[n]) ? mouse_pos - inner_r.Max[n] : (mouse_pos < inner_r.Min[n]) ? mouse_pos - inner_r.Min[n] : 0.0f; + const float scroll_curr = window->Scroll[n]; + if (dist == 0.0f || (dist < 0.0f && scroll_curr < 0.0f) || (dist > 0.0f && scroll_curr >= window->ScrollMax[n])) + continue; + + const float speed_multiplier = ImLinearRemapClamp(g.FontSize, g.FontSize * 5.0f, 1.0f, 4.0f, ImAbs(dist)); // x1 to x4 depending on distance + const float scroll_step = g.FontSize * 35.0f * speed_multiplier * ImSign(dist) * g.IO.DeltaTime; + bs->ScrollAccum[n] += scroll_step; + + // Accumulate into a stored value so we can handle high-framerate + const float scroll_step_i = ImFloor(bs->ScrollAccum[n]); + if (scroll_step_i == 0.0f) + continue; + if (n == 0) + ImGui::SetScrollX(window, scroll_curr + scroll_step_i); + else + ImGui::SetScrollY(window, scroll_curr + scroll_step_i); + bs->ScrollAccum[n] -= scroll_step_i; + } +} + +bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiID box_select_id, ImGuiMultiSelectFlags ms_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiBoxSelectState* bs = &g.BoxSelectState; + KeepAliveID(box_select_id); + if (bs->ID != box_select_id) + return false; + + // IsStarting is set by MultiSelectItemFooter() when considering a possible box-select. We validate it here and lock geometry. + bs->UnclipMode = false; + bs->RequestClear = false; + if (bs->IsStarting && IsMouseDragPastThreshold(0)) + BoxSelectActivateDrag(bs, window); + else if ((bs->IsStarting || bs->IsActive) && g.IO.MouseDown[0] == false) + BoxSelectDeactivateDrag(bs); + if (!bs->IsActive) + return false; + + // Current frame absolute prev/current rectangles are used to toggle selection. + // They are derived from positions relative to scrolling space. + ImVec2 start_pos_abs = WindowPosRelToAbs(window, bs->StartPosRel); + ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, bs->EndPosRel); // Clamped already + ImVec2 curr_end_pos_abs = g.IO.MousePos; + if (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) // Box-select scrolling only happens with ScopeWindow + curr_end_pos_abs = ImClamp(curr_end_pos_abs, scope_rect.Min, scope_rect.Max); + bs->BoxSelectRectPrev.Min = ImMin(start_pos_abs, prev_end_pos_abs); + bs->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs); + bs->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs); + bs->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs); + + // Box-select 2D mode detects horizontal changes (vertical ones are already picked by Clipper) + // Storing an extra rect used by widgets supporting box-select. + if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d) + if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x) + { + bs->UnclipMode = true; + bs->UnclipRect = bs->BoxSelectRectPrev; // FIXME-OPT: UnclipRect x coordinates could be intersection of Prev and Curr rect on X axis. + bs->UnclipRect.Add(bs->BoxSelectRectCurr); + } + + //GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f); + //GetForegroundDrawList()->AddRect(bs->BoxSelectRectPrev.Min, bs->BoxSelectRectPrev.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f); + //GetForegroundDrawList()->AddRect(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectCurr.Max, IM_COL32(0,255,0,200), 0.0f, 0, 1.0f); + return true; +} + +void ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiBoxSelectState* bs = &g.BoxSelectState; + IM_ASSERT(bs->IsActive); + bs->UnclipMode = false; + + // Render selection rectangle + bs->EndPosRel = WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)); // Clamp stored position according to current scrolling view + ImRect box_select_r = bs->BoxSelectRectCurr; + box_select_r.ClipWith(scope_rect); + window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling + window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavCursor)); // FIXME-MULTISELECT: Styling + + // Scroll + const bool enable_scroll = (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms_flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0; + if (enable_scroll) + { + ImRect scroll_r = scope_rect; + scroll_r.Expand(-g.FontSize); + //GetForegroundDrawList()->AddRect(scroll_r.Min, scroll_r.Max, IM_COL32(0, 255, 0, 255)); + if (!scroll_r.Contains(g.IO.MousePos)) + BoxSelectScrollWithMouseDrag(bs, window, scroll_r); + } +} //------------------------------------------------------------------------- // [SECTION] Widgets: Multi-Select support //------------------------------------------------------------------------- +// - DebugLogMultiSelectRequests() [Internal] +// - CalcScopeRect() [Internal] +// - BeginMultiSelect() +// - EndMultiSelect() +// - SetNextItemSelectionUserData() +// - MultiSelectItemHeader() [Internal] +// - MultiSelectItemFooter() [Internal] +// - DebugNodeMultiSelectState() [Internal] +//------------------------------------------------------------------------- + +static void DebugLogMultiSelectRequests(const char* function, const ImGuiMultiSelectIO* io) +{ + ImGuiContext& g = *GImGui; + IM_UNUSED(function); + for (const ImGuiSelectionRequest& req : io->Requests) + { + if (req.Type == ImGuiSelectionRequestType_SetAll) IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetAll %d (= %s)\n", function, req.Selected, req.Selected ? "SelectAll" : "Clear"); + if (req.Type == ImGuiSelectionRequestType_SetRange) IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetRange %" IM_PRId64 "..%" IM_PRId64 " (0x%" IM_PRIX64 "..0x%" IM_PRIX64 ") = %d (dir %d)\n", function, req.RangeFirstItem, req.RangeLastItem, req.RangeFirstItem, req.RangeLastItem, req.Selected, req.RangeDirection); + } +} + +static ImRect CalcScopeRect(ImGuiMultiSelectTempData* ms, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect) + { + // Warning: this depends on CursorMaxPos so it means to be called by EndMultiSelect() only + return ImRect(ms->ScopeRectMin, ImMax(window->DC.CursorMaxPos, ms->ScopeRectMin)); + } + else + { + // When a table, pull HostClipRect, which allows us to predict ClipRect before first row/layout is performed. (#7970) + ImRect scope_rect = window->InnerClipRect; + if (g.CurrentTable != NULL) + scope_rect = g.CurrentTable->HostClipRect; + + // Add inner table decoration (#7821) // FIXME: Why not baking in InnerClipRect? + scope_rect.Min = ImMin(scope_rect.Min + ImVec2(window->DecoInnerSizeX1, window->DecoInnerSizeY1), scope_rect.Max); + return scope_rect; + } +} + +// Return ImGuiMultiSelectIO structure. +// Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect(). +// Passing 'selection_size' and 'items_count' parameters is currently optional. +// - 'selection_size' is useful to disable some shortcut routing: e.g. ImGuiMultiSelectFlags_ClearOnEscape won't claim Escape key when selection_size 0, +// allowing a first press to clear selection THEN the second press to leave child window and return to parent. +// - 'items_count' is stored in ImGuiMultiSelectIO which makes it a convenient way to pass the information to your ApplyRequest() handler (but you may pass it differently). +// - If they are costly for you to compute (e.g. external intrusive selection without maintaining size), you may avoid them and pass -1. +// - If you can easily tell if your selection is empty or not, you may pass 0/1, or you may enable ImGuiMultiSelectFlags_ClearOnEscape flag dynamically. +ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int selection_size, int items_count) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (++g.MultiSelectTempDataStacked > g.MultiSelectTempData.Size) + g.MultiSelectTempData.resize(g.MultiSelectTempDataStacked, ImGuiMultiSelectTempData()); + ImGuiMultiSelectTempData* ms = &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1]; + IM_STATIC_ASSERT(offsetof(ImGuiMultiSelectTempData, IO) == 0); // Clear() relies on that. + g.CurrentMultiSelect = ms; + if ((flags & (ImGuiMultiSelectFlags_ScopeWindow | ImGuiMultiSelectFlags_ScopeRect)) == 0) + flags |= ImGuiMultiSelectFlags_ScopeWindow; + if (flags & ImGuiMultiSelectFlags_SingleSelect) + flags &= ~(ImGuiMultiSelectFlags_BoxSelect2d | ImGuiMultiSelectFlags_BoxSelect1d); + if (flags & ImGuiMultiSelectFlags_BoxSelect2d) + flags &= ~ImGuiMultiSelectFlags_BoxSelect1d; + + // FIXME: BeginFocusScope() + const ImGuiID id = window->IDStack.back(); + ms->Clear(); + ms->FocusScopeId = id; + ms->Flags = flags; + ms->IsFocused = (ms->FocusScopeId == g.NavFocusScopeId); + ms->BackupCursorMaxPos = window->DC.CursorMaxPos; + ms->ScopeRectMin = window->DC.CursorMaxPos = window->DC.CursorPos; + PushFocusScope(ms->FocusScopeId); + if (flags & ImGuiMultiSelectFlags_ScopeWindow) // Mark parent child window as navigable into, with highlight. Assume user will always submit interactive items. + window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main; + + // Use copy of keyboard mods at the time of the request, otherwise we would requires mods to be held for an extra frame. + ms->KeyMods = g.NavJustMovedToId ? (g.NavJustMovedToIsTabbing ? 0 : g.NavJustMovedToKeyMods) : g.IO.KeyMods; + if (flags & ImGuiMultiSelectFlags_NoRangeSelect) + ms->KeyMods &= ~ImGuiMod_Shift; + + // Bind storage + ImGuiMultiSelectState* storage = g.MultiSelectStorage.GetOrAddByKey(id); + storage->ID = id; + storage->LastFrameActive = g.FrameCount; + storage->LastSelectionSize = selection_size; + storage->Window = window; + ms->Storage = storage; + + // Output to user + ms->IO.Requests.resize(0); + ms->IO.RangeSrcItem = storage->RangeSrcItem; + ms->IO.NavIdItem = storage->NavIdItem; + ms->IO.NavIdSelected = (storage->NavIdSelected == 1) ? true : false; + ms->IO.ItemsCount = items_count; + + // Clear when using Navigation to move within the scope + // (we compare FocusScopeId so it possible to use multiple selections inside a same window) + bool request_clear = false; + bool request_select_all = false; + if (g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == ms->FocusScopeId && g.NavJustMovedToHasSelectionData) + { + if (ms->KeyMods & ImGuiMod_Shift) + ms->IsKeyboardSetRange = true; + if (ms->IsKeyboardSetRange) + IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid); // Not ready -> could clear? + if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0) + request_clear = true; + } + else if (g.NavJustMovedFromFocusScopeId == ms->FocusScopeId) + { + // Also clear on leaving scope (may be optional?) + if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0) + request_clear = true; + } + + // Box-select handling: update active state. + ImGuiBoxSelectState* bs = &g.BoxSelectState; + if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) + { + ms->BoxSelectId = GetID("##BoxSelect"); + if (BeginBoxSelect(CalcScopeRect(ms, window), window, ms->BoxSelectId, flags)) + request_clear |= bs->RequestClear; + } + + if (ms->IsFocused) + { + // Shortcut: Clear selection (Escape) + // - Only claim shortcut if selection is not empty, allowing further presses on Escape to e.g. leave current child window. + // - Box select also handle Escape and needs to pass an id to bypass ActiveIdUsingAllKeyboardKeys lock. + if (flags & ImGuiMultiSelectFlags_ClearOnEscape) + { + if (selection_size != 0 || bs->IsActive) + if (Shortcut(ImGuiKey_Escape, ImGuiInputFlags_None, bs->IsActive ? bs->ID : 0)) + { + request_clear = true; + if (bs->IsActive) + BoxSelectDeactivateDrag(bs); + } + } + + // Shortcut: Select all (CTRL+A) + if (!(flags & ImGuiMultiSelectFlags_SingleSelect) && !(flags & ImGuiMultiSelectFlags_NoSelectAll)) + if (Shortcut(ImGuiMod_Ctrl | ImGuiKey_A)) + request_select_all = true; + } + + if (request_clear || request_select_all) + { + MultiSelectAddSetAll(ms, request_select_all); + if (!request_select_all) + storage->LastSelectionSize = 0; + } + ms->LoopRequestSetAll = request_select_all ? 1 : request_clear ? 0 : -1; + ms->LastSubmittedItem = ImGuiSelectionUserData_Invalid; + + if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) + DebugLogMultiSelectRequests("BeginMultiSelect", &ms->IO); + + return &ms->IO; +} + +// Return updated ImGuiMultiSelectIO structure. +// Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect(). +ImGuiMultiSelectIO* ImGui::EndMultiSelect() +{ + ImGuiContext& g = *GImGui; + ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect; + ImGuiMultiSelectState* storage = ms->Storage; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT_USER_ERROR(ms->FocusScopeId == g.CurrentFocusScopeId, "EndMultiSelect() FocusScope mismatch!"); + IM_ASSERT(g.CurrentMultiSelect != NULL && storage->Window == g.CurrentWindow); + IM_ASSERT(g.MultiSelectTempDataStacked > 0 && &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] == g.CurrentMultiSelect); + + ImRect scope_rect = CalcScopeRect(ms, window); + if (ms->IsFocused) + { + // We currently don't allow user code to modify RangeSrcItem by writing to BeginIO's version, but that would be an easy change here. + if (ms->IO.RangeSrcReset || (ms->RangeSrcPassedBy == false && ms->IO.RangeSrcItem != ImGuiSelectionUserData_Invalid)) // Can't read storage->RangeSrcItem here -> we want the state at begining of the scope (see tests for easy failure) + { + IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset RangeSrcItem.\n"); // Will set be to NavId. + storage->RangeSrcItem = ImGuiSelectionUserData_Invalid; + } + if (ms->NavIdPassedBy == false && storage->NavIdItem != ImGuiSelectionUserData_Invalid) + { + IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset NavIdItem.\n"); + storage->NavIdItem = ImGuiSelectionUserData_Invalid; + storage->NavIdSelected = -1; + } + + if ((ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) && GetBoxSelectState(ms->BoxSelectId)) + EndBoxSelect(scope_rect, ms->Flags); + } + + if (ms->IsEndIO == false) + ms->IO.Requests.resize(0); + + // Clear selection when clicking void? + // We specifically test for IsMouseDragPastThreshold(0) == false to allow box-selection! + // The InnerRect test is necessary for non-child/decorated windows. + bool scope_hovered = IsWindowHovered() && window->InnerRect.Contains(g.IO.MousePos); + if (scope_hovered && (ms->Flags & ImGuiMultiSelectFlags_ScopeRect)) + scope_hovered &= scope_rect.Contains(g.IO.MousePos); + if (scope_hovered && g.HoveredId == 0 && g.ActiveId == 0) + { + if (ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) + { + if (!g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && g.IO.MouseClickedCount[0] == 1) + { + BoxSelectPreStartDrag(ms->BoxSelectId, ImGuiSelectionUserData_Invalid); + FocusWindow(window, ImGuiFocusRequestFlags_UnlessBelowModal); + SetHoveredID(ms->BoxSelectId); + if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect) + SetNavID(0, ImGuiNavLayer_Main, ms->FocusScopeId, ImRect(g.IO.MousePos, g.IO.MousePos)); // Automatically switch FocusScope for initial click from void to box-select. + } + } + + if (ms->Flags & ImGuiMultiSelectFlags_ClearOnClickVoid) + if (IsMouseReleased(0) && IsMouseDragPastThreshold(0) == false && g.IO.KeyMods == ImGuiMod_None) + MultiSelectAddSetAll(ms, false); + } + + // Courtesy nav wrapping helper flag + if (ms->Flags & ImGuiMultiSelectFlags_NavWrapX) + { + IM_ASSERT(ms->Flags & ImGuiMultiSelectFlags_ScopeWindow); // Only supported at window scope + ImGui::NavMoveRequestTryWrapping(ImGui::GetCurrentWindow(), ImGuiNavMoveFlags_WrapX); + } + + // Unwind + window->DC.CursorMaxPos = ImMax(ms->BackupCursorMaxPos, window->DC.CursorMaxPos); + PopFocusScope(); + + if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) + DebugLogMultiSelectRequests("EndMultiSelect", &ms->IO); + + ms->FocusScopeId = 0; + ms->Flags = ImGuiMultiSelectFlags_None; + g.CurrentMultiSelect = (--g.MultiSelectTempDataStacked > 0) ? &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] : NULL; + + return &ms->IO; +} void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data) { // Note that flags will be cleared by ItemAdd(), so it's only useful for Navigation code! // This designed so widgets can also cheaply set this before calling ItemAdd(), so we are not tied to MultiSelect api. ImGuiContext& g = *GImGui; - g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData; g.NextItemData.SelectionUserData = selection_user_data; + g.NextItemData.FocusScopeId = g.CurrentFocusScopeId; + + if (ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect) + { + // Auto updating RangeSrcPassedBy for cases were clipper is not used (done before ItemAdd() clipping) + g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect; + if (ms->IO.RangeSrcItem == selection_user_data) + ms->RangeSrcPassedBy = true; + } + else + { + g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData; + } +} + +// In charge of: +// - Applying SetAll for submitted items. +// - Applying SetRange for submitted items and record end points. +// - Altering button behavior flags to facilitate use with drag and drop. +void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags* p_button_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect; + + bool selected = *p_selected; + if (ms->IsFocused) + { + ImGuiMultiSelectState* storage = ms->Storage; + ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData; + IM_ASSERT(g.NextItemData.FocusScopeId == g.CurrentFocusScopeId && "Forgot to call SetNextItemSelectionUserData() prior to item, required in BeginMultiSelect()/EndMultiSelect() scope"); + + // Apply SetAll (Clear/SelectAll) requests requested by BeginMultiSelect(). + // This is only useful if the user hasn't processed them already, and this only works if the user isn't using the clipper. + // If you are using a clipper you need to process the SetAll request after calling BeginMultiSelect() + if (ms->LoopRequestSetAll != -1) + selected = (ms->LoopRequestSetAll == 1); + + // When using SHIFT+Nav: because it can incur scrolling we cannot afford a frame of lag with the selection highlight (otherwise scrolling would happen before selection) + // For this to work, we need someone to set 'RangeSrcPassedBy = true' at some point (either clipper either SetNextItemSelectionUserData() function) + if (ms->IsKeyboardSetRange) + { + IM_ASSERT(id != 0 && (ms->KeyMods & ImGuiMod_Shift) != 0); + const bool is_range_dst = (ms->RangeDstPassedBy == false) && g.NavJustMovedToId == id; // Assume that g.NavJustMovedToId is not clipped. + if (is_range_dst) + ms->RangeDstPassedBy = true; + if (is_range_dst && storage->RangeSrcItem == ImGuiSelectionUserData_Invalid) // If we don't have RangeSrc, assign RangeSrc = RangeDst + { + storage->RangeSrcItem = item_data; + storage->RangeSelected = selected ? 1 : 0; + } + const bool is_range_src = storage->RangeSrcItem == item_data; + if (is_range_src || is_range_dst || ms->RangeSrcPassedBy != ms->RangeDstPassedBy) + { + // Apply range-select value to visible items + IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid && storage->RangeSelected != -1); + selected = (storage->RangeSelected != 0); + } + else if ((ms->KeyMods & ImGuiMod_Ctrl) == 0 && (ms->Flags & ImGuiMultiSelectFlags_NoAutoClear) == 0) + { + // Clear other items + selected = false; + } + } + *p_selected = selected; + } + + // Alter button behavior flags + // To handle drag and drop of multiple items we need to avoid clearing selection on click. + // Enabling this test makes actions using CTRL+SHIFT delay their effect on MouseUp which is annoying, but it allows drag and drop of multiple items. + if (p_button_flags != NULL) + { + ImGuiButtonFlags button_flags = *p_button_flags; + button_flags |= ImGuiButtonFlags_NoHoveredOnFocus; + if ((!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore)) && !(ms->Flags & ImGuiMultiSelectFlags_SelectOnClickRelease)) + button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease; + else + button_flags |= ImGuiButtonFlags_PressedOnClickRelease; + *p_button_flags = button_flags; + } +} + +// In charge of: +// - Auto-select on navigation. +// - Box-select toggle handling. +// - Right-click handling. +// - Altering selection based on Ctrl/Shift modifiers, both for keyboard and mouse. +// - Record current selection state for RangeSrc +// This is all rather complex, best to run and refer to "widgets_multiselect_xxx" tests in imgui_test_suite. +void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + bool selected = *p_selected; + bool pressed = *p_pressed; + ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect; + ImGuiMultiSelectState* storage = ms->Storage; + if (pressed) + ms->IsFocused = true; + + bool hovered = false; + if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) + hovered = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); + if (!ms->IsFocused && !hovered) + return; + + ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData; + + ImGuiMultiSelectFlags flags = ms->Flags; + const bool is_singleselect = (flags & ImGuiMultiSelectFlags_SingleSelect) != 0; + bool is_ctrl = (ms->KeyMods & ImGuiMod_Ctrl) != 0; + bool is_shift = (ms->KeyMods & ImGuiMod_Shift) != 0; + + bool apply_to_range_src = false; + + if (g.NavId == id && storage->RangeSrcItem == ImGuiSelectionUserData_Invalid) + apply_to_range_src = true; + if (ms->IsEndIO == false) + { + ms->IO.Requests.resize(0); + ms->IsEndIO = true; + } + + // Auto-select as you navigate a list + if (g.NavJustMovedToId == id) + { + if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0) + { + if (is_ctrl && is_shift) + pressed = true; + else if (!is_ctrl) + selected = pressed = true; + } + else + { + // With NoAutoSelect, using Shift+keyboard performs a write/copy + if (is_shift) + pressed = true; + else if (!is_ctrl) + apply_to_range_src = true; // Since if (pressed) {} main block is not running we update this + } + } + + if (apply_to_range_src) + { + storage->RangeSrcItem = item_data; + storage->RangeSelected = selected; // Will be updated at the end of this function anyway. + } + + // Box-select toggle handling + if (ms->BoxSelectId != 0) + if (ImGuiBoxSelectState* bs = GetBoxSelectState(ms->BoxSelectId)) + { + const bool rect_overlap_curr = bs->BoxSelectRectCurr.Overlaps(g.LastItemData.Rect); + const bool rect_overlap_prev = bs->BoxSelectRectPrev.Overlaps(g.LastItemData.Rect); + if ((rect_overlap_curr && !rect_overlap_prev && !selected) || (rect_overlap_prev && !rect_overlap_curr)) + { + if (storage->LastSelectionSize <= 0 && bs->IsStartedSetNavIdOnce) + { + pressed = true; // First item act as a pressed: code below will emit selection request and set NavId (whatever we emit here will be overridden anyway) + bs->IsStartedSetNavIdOnce = false; + } + else + { + selected = !selected; + MultiSelectAddSetRange(ms, selected, +1, item_data, item_data); + } + storage->LastSelectionSize = ImMax(storage->LastSelectionSize + 1, 1); + } + } + + // Right-click handling. + // FIXME-MULTISELECT: Currently filtered out by ImGuiMultiSelectFlags_NoAutoSelect but maybe should be moved to Selectable(). See https://github.com/ocornut/imgui/pull/5816 + if (hovered && IsMouseClicked(1) && (flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0) + { + if (g.ActiveId != 0 && g.ActiveId != id) + ClearActiveID(); + SetFocusID(id, window); + if (!pressed && !selected) + { + pressed = true; + is_ctrl = is_shift = false; + } + } + + // Unlike Space, Enter doesn't alter selection (but can still return a press) unless current item is not selected. + // The later, "unless current item is not select", may become optional? It seems like a better default if Enter doesn't necessarily open something + // (unlike e.g. Windows explorer). For use case where Enter always open something, we might decide to make this optional? + const bool enter_pressed = pressed && (g.NavActivateId == id) && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput); + + // Alter selection + if (pressed && (!enter_pressed || !selected)) + { + // Box-select + ImGuiInputSource input_source = (g.NavJustMovedToId == id || g.NavActivateId == id) ? g.NavInputSource : ImGuiInputSource_Mouse; + if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) + if (selected == false && !g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && input_source == ImGuiInputSource_Mouse && g.IO.MouseClickedCount[0] == 1) + BoxSelectPreStartDrag(ms->BoxSelectId, item_data); + + //---------------------------------------------------------------------------------------- + // ACTION | Begin | Pressed/Activated | End + //---------------------------------------------------------------------------------------- + // Keys Navigated: | Clear | Src=item, Sel=1 SetRange 1 + // Keys Navigated: Ctrl | n/a | n/a + // Keys Navigated: Shift | n/a | Dst=item, Sel=1, => Clear + SetRange 1 + // Keys Navigated: Ctrl+Shift | n/a | Dst=item, Sel=Src => Clear + SetRange Src-Dst + // Keys Activated: | n/a | Src=item, Sel=1 => Clear + SetRange 1 + // Keys Activated: Ctrl | n/a | Src=item, Sel=!Sel => SetSange 1 + // Keys Activated: Shift | n/a | Dst=item, Sel=1 => Clear + SetSange 1 + //---------------------------------------------------------------------------------------- + // Mouse Pressed: | n/a | Src=item, Sel=1, => Clear + SetRange 1 + // Mouse Pressed: Ctrl | n/a | Src=item, Sel=!Sel => SetRange 1 + // Mouse Pressed: Shift | n/a | Dst=item, Sel=1, => Clear + SetRange 1 + // Mouse Pressed: Ctrl+Shift | n/a | Dst=item, Sel=!Sel => SetRange Src-Dst + //---------------------------------------------------------------------------------------- + + if ((flags & ImGuiMultiSelectFlags_NoAutoClear) == 0) + { + bool request_clear = false; + if (is_singleselect) + request_clear = true; + else if ((input_source == ImGuiInputSource_Mouse || g.NavActivateId == id) && !is_ctrl) + request_clear = (flags & ImGuiMultiSelectFlags_NoAutoClearOnReselect) ? !selected : true; + else if ((input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Gamepad) && is_shift && !is_ctrl) + request_clear = true; // With is_shift==false the RequestClear was done in BeginIO, not necessary to do again. + if (request_clear) + MultiSelectAddSetAll(ms, false); + } + + int range_direction; + bool range_selected; + if (is_shift && !is_singleselect) + { + //IM_ASSERT(storage->HasRangeSrc && storage->HasRangeValue); + if (storage->RangeSrcItem == ImGuiSelectionUserData_Invalid) + storage->RangeSrcItem = item_data; + if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0) + { + // Shift+Arrow always select + // Ctrl+Shift+Arrow copy source selection state (already stored by BeginMultiSelect() in storage->RangeSelected) + range_selected = (is_ctrl && storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true; + } + else + { + // Shift+Arrow copy source selection state + // Shift+Click always copy from target selection state + if (ms->IsKeyboardSetRange) + range_selected = (storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true; + else + range_selected = !selected; + } + range_direction = ms->RangeSrcPassedBy ? +1 : -1; + } + else + { + // Ctrl inverts selection, otherwise always select + if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0) + selected = is_ctrl ? !selected : true; + else + selected = !selected; + storage->RangeSrcItem = item_data; + range_selected = selected; + range_direction = +1; + } + MultiSelectAddSetRange(ms, range_selected, range_direction, storage->RangeSrcItem, item_data); + } + + // Update/store the selection state of the Source item (used by CTRL+SHIFT, when Source is unselected we perform a range unselect) + if (storage->RangeSrcItem == item_data) + storage->RangeSelected = selected ? 1 : 0; + + // Update/store the selection state of focused item + if (g.NavId == id) + { + storage->NavIdItem = item_data; + storage->NavIdSelected = selected ? 1 : 0; + } + if (storage->NavIdItem == item_data) + ms->NavIdPassedBy = true; + ms->LastSubmittedItem = item_data; + + *p_selected = selected; + *p_pressed = pressed; +} + +void ImGui::MultiSelectAddSetAll(ImGuiMultiSelectTempData* ms, bool selected) +{ + ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetAll, selected, 0, ImGuiSelectionUserData_Invalid, ImGuiSelectionUserData_Invalid }; + ms->IO.Requests.resize(0); // Can always clear previous requests + ms->IO.Requests.push_back(req); // Add new request +} + +void ImGui::MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item) +{ + // Merge contiguous spans into same request (unless NoRangeSelect is set which guarantees single-item ranges) + if (ms->IO.Requests.Size > 0 && first_item == last_item && (ms->Flags & ImGuiMultiSelectFlags_NoRangeSelect) == 0) + { + ImGuiSelectionRequest* prev = &ms->IO.Requests.Data[ms->IO.Requests.Size - 1]; + if (prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->LastSubmittedItem && prev->Selected == selected) + { + prev->RangeLastItem = last_item; + return; + } + } + + ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, (ImS8)range_dir, (range_dir > 0) ? first_item : last_item, (range_dir > 0) ? last_item : first_item }; + ms->IO.Requests.push_back(req); // Add new request +} + +void ImGui::DebugNodeMultiSelectState(ImGuiMultiSelectState* storage) +{ +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + const bool is_active = (storage->LastFrameActive >= GetFrameCount() - 2); // Note that fully clipped early out scrolling tables will appear as inactive here. + if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); } + bool open = TreeNode((void*)(intptr_t)storage->ID, "MultiSelect 0x%08X in '%s'%s", storage->ID, storage->Window ? storage->Window->Name : "N/A", is_active ? "" : " *Inactive*"); + if (!is_active) { PopStyleColor(); } + if (!open) + return; + Text("RangeSrcItem = %" IM_PRId64 " (0x%" IM_PRIX64 "), RangeSelected = %d", storage->RangeSrcItem, storage->RangeSrcItem, storage->RangeSelected); + Text("NavIdItem = %" IM_PRId64 " (0x%" IM_PRIX64 "), NavIdSelected = %d", storage->NavIdItem, storage->NavIdItem, storage->NavIdSelected); + Text("LastSelectionSize = %d", storage->LastSelectionSize); // Provided by user + TreePop(); +#else + IM_UNUSED(storage); +#endif } +//------------------------------------------------------------------------- +// [SECTION] Widgets: Multi-Select helpers +//------------------------------------------------------------------------- +// - ImGuiSelectionBasicStorage +// - ImGuiSelectionExternalStorage +//------------------------------------------------------------------------- + +ImGuiSelectionBasicStorage::ImGuiSelectionBasicStorage() +{ + Size = 0; + PreserveOrder = false; + UserData = NULL; + AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage*, int idx) { return (ImGuiID)idx; }; + _SelectionOrder = 1; // Always >0 +} + +void ImGuiSelectionBasicStorage::Clear() +{ + Size = 0; + _SelectionOrder = 1; // Always >0 + _Storage.Data.resize(0); +} + +void ImGuiSelectionBasicStorage::Swap(ImGuiSelectionBasicStorage& r) +{ + ImSwap(Size, r.Size); + ImSwap(_SelectionOrder, r._SelectionOrder); + _Storage.Data.swap(r._Storage.Data); +} + +bool ImGuiSelectionBasicStorage::Contains(ImGuiID id) const +{ + return _Storage.GetInt(id, 0) != 0; +} + +static int IMGUI_CDECL PairComparerByValueInt(const void* lhs, const void* rhs) +{ + int lhs_v = ((const ImGuiStoragePair*)lhs)->val_i; + int rhs_v = ((const ImGuiStoragePair*)rhs)->val_i; + return (lhs_v > rhs_v ? +1 : lhs_v < rhs_v ? -1 : 0); +} + +// GetNextSelectedItem() is an abstraction allowing us to change our underlying actual storage system without impacting user. +// (e.g. store unselected vs compact down, compact down on demand, use raw ImVector instead of ImGuiStorage...) +bool ImGuiSelectionBasicStorage::GetNextSelectedItem(void** opaque_it, ImGuiID* out_id) +{ + ImGuiStoragePair* it = (ImGuiStoragePair*)*opaque_it; + ImGuiStoragePair* it_end = _Storage.Data.Data + _Storage.Data.Size; + if (PreserveOrder && it == NULL && it_end != NULL) + ImQsort(_Storage.Data.Data, (size_t)_Storage.Data.Size, sizeof(ImGuiStoragePair), PairComparerByValueInt); // ~ImGuiStorage::BuildSortByValueInt() + if (it == NULL) + it = _Storage.Data.Data; + IM_ASSERT(it >= _Storage.Data.Data && it <= it_end); + if (it != it_end) + while (it->val_i == 0 && it < it_end) + it++; + const bool has_more = (it != it_end); + *opaque_it = has_more ? (void**)(it + 1) : (void**)(it); + *out_id = has_more ? it->key : 0; + if (PreserveOrder && !has_more) + _Storage.BuildSortByKey(); + return has_more; +} + +void ImGuiSelectionBasicStorage::SetItemSelected(ImGuiID id, bool selected) +{ + int* p_int = _Storage.GetIntRef(id, 0); + if (selected && *p_int == 0) { *p_int = _SelectionOrder++; Size++; } + else if (!selected && *p_int != 0) { *p_int = 0; Size--; } +} + +// Optimized for batch edits (with same value of 'selected') +static void ImGuiSelectionBasicStorage_BatchSetItemSelected(ImGuiSelectionBasicStorage* selection, ImGuiID id, bool selected, int size_before_amends, int selection_order) +{ + ImGuiStorage* storage = &selection->_Storage; + ImGuiStoragePair* it = ImLowerBound(storage->Data.Data, storage->Data.Data + size_before_amends, id); + const bool is_contained = (it != storage->Data.Data + size_before_amends) && (it->key == id); + if (selected == (is_contained && it->val_i != 0)) + return; + if (selected && !is_contained) + storage->Data.push_back(ImGuiStoragePair(id, selection_order)); // Push unsorted at end of vector, will be sorted in SelectionMultiAmendsFinish() + else if (is_contained) + it->val_i = selected ? selection_order : 0; // Modify in-place. + selection->Size += selected ? +1 : -1; +} + +static void ImGuiSelectionBasicStorage_BatchFinish(ImGuiSelectionBasicStorage* selection, bool selected, int size_before_amends) +{ + ImGuiStorage* storage = &selection->_Storage; + if (selected && selection->Size != size_before_amends) + storage->BuildSortByKey(); // When done selecting: sort everything +} + +// Apply requests coming from BeginMultiSelect() and EndMultiSelect(). +// - Enable 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen. +// - Honoring SetRange requests requires that you can iterate/interpolate between RangeFirstItem and RangeLastItem. +// - In this demo we often submit indices to SetNextItemSelectionUserData() + store the same indices in persistent selection. +// - Your code may do differently. If you store pointers or objects ID in ImGuiSelectionUserData you may need to perform +// a lookup in order to have some way to iterate/interpolate between two items. +// - A full-featured application is likely to allow search/filtering which is likely to lead to using indices +// and constructing a view index <> object id/ptr data structure anyway. +// WHEN YOUR APPLICATION SETTLES ON A CHOICE, YOU WILL PROBABLY PREFER TO GET RID OF THIS UNNECESSARY 'ImGuiSelectionBasicStorage' INDIRECTION LOGIC. +// Notice that with the simplest adapter (using indices everywhere), all functions return their parameters. +// The most simple implementation (using indices everywhere) would look like: +// for (ImGuiSelectionRequest& req : ms_io->Requests) +// { +// if (req.Type == ImGuiSelectionRequestType_SetAll) { Clear(); if (req.Selected) { for (int n = 0; n < items_count; n++) { SetItemSelected(n, true); } } +// if (req.Type == ImGuiSelectionRequestType_SetRange) { for (int n = (int)ms_io->RangeFirstItem; n <= (int)ms_io->RangeLastItem; n++) { SetItemSelected(n, ms_io->Selected); } } +// } +void ImGuiSelectionBasicStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io) +{ + // For convenience we obtain ItemsCount as passed to BeginMultiSelect(), which is optional. + // It makes sense when using ImGuiSelectionBasicStorage to simply pass your items count to BeginMultiSelect(). + // Other scheme may handle SetAll differently. + IM_ASSERT(ms_io->ItemsCount != -1 && "Missing value for items_count in BeginMultiSelect() call!"); + IM_ASSERT(AdapterIndexToStorageId != NULL); + + // This is optimized/specialized to cope with very large selections (e.g. 100k+ items) + // - A simpler version could call SetItemSelected() directly instead of ImGuiSelectionBasicStorage_BatchSetItemSelected() + ImGuiSelectionBasicStorage_BatchFinish(). + // - Optimized select can append unsorted, then sort in a second pass. Optimized unselect can clear in-place then compact in a second pass. + // - A more optimal version wouldn't even use ImGuiStorage but directly a ImVector to reduce bandwidth, but this is a reasonable trade off to reuse code. + // - There are many ways this could be better optimized. The worse case scenario being: using BoxSelect2d in a grid, box-select scrolling down while wiggling + // left and right: it affects coarse clipping + can emit multiple SetRange with 1 item each.) + // FIXME-OPT: For each block of consecutive SetRange request: + // - add all requests to a sorted list, store ID, selected, offset in ImGuiStorage. + // - rewrite sorted storage a single time. + for (ImGuiSelectionRequest& req : ms_io->Requests) + { + if (req.Type == ImGuiSelectionRequestType_SetAll) + { + Clear(); + if (req.Selected) + { + _Storage.Data.reserve(ms_io->ItemsCount); + const int size_before_amends = _Storage.Data.Size; + for (int idx = 0; idx < ms_io->ItemsCount; idx++, _SelectionOrder++) + ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends, _SelectionOrder); + ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends); + } + } + else if (req.Type == ImGuiSelectionRequestType_SetRange) + { + const int selection_changes = (int)req.RangeLastItem - (int)req.RangeFirstItem + 1; + //ImGuiContext& g = *GImGui; IMGUI_DEBUG_LOG_SELECTION("Req %d/%d: set %d to %d\n", ms_io->Requests.index_from_ptr(&req), ms_io->Requests.Size, selection_changes, req.Selected); + if (selection_changes == 1 || (selection_changes < Size / 100)) + { + // Multiple sorted insertion + copy likely to be faster. + // Technically we could do a single copy with a little more work (sort sequential SetRange requests) + for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++) + SetItemSelected(GetStorageIdFromIndex(idx), req.Selected); + } + else + { + // Append insertion + single sort likely be faster. + // Use req.RangeDirection to set order field so that shift+clicking from 1 to 5 is different than shift+clicking from 5 to 1 + const int size_before_amends = _Storage.Data.Size; + int selection_order = _SelectionOrder + ((req.RangeDirection < 0) ? selection_changes - 1 : 0); + for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++, selection_order += req.RangeDirection) + ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends, selection_order); + if (req.Selected) + _SelectionOrder += selection_changes; + ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends); + } + } + } +} + +//------------------------------------------------------------------------- + +ImGuiSelectionExternalStorage::ImGuiSelectionExternalStorage() +{ + UserData = NULL; + AdapterSetItemSelected = NULL; +} + +// Apply requests coming from BeginMultiSelect() and EndMultiSelect(). +// We also pull 'ms_io->ItemsCount' as passed for BeginMultiSelect() for consistency with ImGuiSelectionBasicStorage +// This makes no assumption about underlying storage. +void ImGuiSelectionExternalStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io) +{ + IM_ASSERT(AdapterSetItemSelected); + for (ImGuiSelectionRequest& req : ms_io->Requests) + { + if (req.Type == ImGuiSelectionRequestType_SetAll) + for (int idx = 0; idx < ms_io->ItemsCount; idx++) + AdapterSetItemSelected(this, idx, req.Selected); + if (req.Type == ImGuiSelectionRequestType_SetRange) + for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++) + AdapterSetItemSelected(this, idx, req.Selected); + } +} //------------------------------------------------------------------------- // [SECTION] Widgets: ListBox @@ -6954,6 +8213,13 @@ void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_d //------------------------------------------------------------------------- // This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label. +// This handle some subtleties with capturing info from the label, but for 99% uses it could essentially be rewritten as: +// if (ImGui::BeginChild("...", ImVec2(ImGui::CalcItemWidth(), ImGui::GetTextLineHeight() * 7.5f), ImGuiChildFlags_FrameStyle)) +// { .... } +// ImGui::EndChild(); +// ImGui::SameLine(); +// ImGui::AlignTextToFramePadding(); +// ImGui::Text("Label"); // Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. "##empty" // Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling boundaries (e.g. 10.25 * item_height). bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg) @@ -7091,9 +8357,10 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0)); ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, 0, &frame_bb)) + if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_NoNav)) return -1; - const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags); + bool hovered; + ButtonBehavior(frame_bb, id, &hovered, NULL); // Determine scale from values if not specified if (scale_min == FLT_MAX || scale_max == FLT_MAX) @@ -7338,7 +8605,7 @@ bool ImGui::BeginMenuBar() // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect. // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy. ImRect bar_rect = window->MenuBarRect(); - ImRect clip_rect(IM_ROUND(bar_rect.Min.x + window->WindowBorderSize), IM_ROUND(bar_rect.Min.y + window->WindowBorderSize), IM_ROUND(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, window->WindowBorderSize))), IM_ROUND(bar_rect.Max.y)); + ImRect clip_rect(ImFloor(bar_rect.Min.x + window->WindowBorderSize), ImFloor(bar_rect.Min.y + window->WindowBorderSize), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, window->WindowBorderSize))), ImFloor(bar_rect.Max.y)); clip_rect.ClipWith(window->OuterRectClipped); PushClipRect(clip_rect.Min, clip_rect.Max, false); @@ -7374,8 +8641,13 @@ void ImGui::EndMenuBar() IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check (FIXME: Seems unnecessary) FocusWindow(window); SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]); - g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection. - g.NavDisableMouseHover = g.NavMousePosDirty = true; + // FIXME-NAV: How to deal with this when not using g.IO.ConfigNavCursorVisibleAuto? + if (g.NavCursorVisible) + { + g.NavCursorVisible = false; // Hide nav cursor for the current frame so we don't see the intermediary selection. Will be set again + g.NavCursorHideFrames = 2; + } + g.NavHighlightItemUnderNav = g.NavMousePosDirty = true; NavMoveRequestForward(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags); // Repeat } } @@ -7408,10 +8680,10 @@ bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, Im IM_ASSERT(dir != ImGuiDir_None); ImGuiWindow* bar_window = FindWindowByName(name); + ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport()); if (bar_window == NULL || bar_window->BeginCount == 0) { // Calculate and set window size/position - ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport()); ImRect avail_rect = viewport->GetBuildWorkRect(); ImGuiAxis axis = (dir == ImGuiDir_Up || dir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X; ImVec2 pos = avail_rect.Min; @@ -7424,12 +8696,13 @@ bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, Im // Report our size into work area (for next frame) using actual window size if (dir == ImGuiDir_Up || dir == ImGuiDir_Left) - viewport->BuildWorkOffsetMin[axis] += axis_size; + viewport->BuildWorkInsetMin[axis] += axis_size; else if (dir == ImGuiDir_Down || dir == ImGuiDir_Right) - viewport->BuildWorkOffsetMax[axis] -= axis_size; + viewport->BuildWorkInsetMax[axis] += axis_size; } - window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; + window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking; + SetNextWindowViewport(viewport->ID); // Enforce viewport so we don't create our own viewport when ImGuiConfigFlags_ViewportsNoMerge is set. PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint bool is_open = Begin(name, NULL, window_flags); @@ -7443,6 +8716,9 @@ bool ImGui::BeginMainMenuBar() ImGuiContext& g = *GImGui; ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport(); + // Notify of viewport change so GetFrameHeight() can be accurate in case of DPI change + SetCurrentViewport(NULL, viewport); + // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set. // FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea? // FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings. @@ -7493,7 +8769,7 @@ static bool IsRootOfOpenMenuSet() const ImGuiPopupData* upper_popup = &g.OpenPopupStack[g.BeginPopupStack.Size]; if (window->DC.NavLayerCurrent != upper_popup->ParentNavLayer) return false; - return upper_popup->Window && (upper_popup->Window->Flags & ImGuiWindowFlags_ChildMenu) && ImGui::IsWindowChildOf(upper_popup->Window, window, true); + return upper_popup->Window && (upper_popup->Window->Flags & ImGuiWindowFlags_ChildMenu) && ImGui::IsWindowChildOf(upper_popup->Window, window, true, false); } bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) @@ -7547,7 +8823,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) bool pressed; // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another. - const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups; + const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_NoAutoClosePopups; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { // Menu inside an horizontal menu bar @@ -7555,10 +8831,11 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin() popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight); window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f); - PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); + PushStyleVarX(ImGuiStyleVar_ItemSpacing, style.ItemSpacing.x * 2.0f); float w = label_size.x; ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); pressed = Selectable("", menu_is_open, selectable_flags, ImVec2(w, label_size.y)); + LogSetNextTextDecoration("[", "]"); RenderText(text_pos, label); PopStyleVar(); window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). @@ -7575,6 +8852,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y)); + LogSetNextTextDecoration("", ">"); RenderText(text_pos, label); if (icon_w > 0.0f) RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); @@ -7583,7 +8861,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) if (!enabled) EndDisabled(); - const bool hovered = (g.HoveredId == id) && enabled && !g.NavDisableMouseHover; + const bool hovered = (g.HoveredId == id) && enabled && !g.NavHighlightItemUnderNav; if (menuset_is_open) PopItemFlag(); @@ -7618,7 +8896,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) // The 'HovereWindow == window' check creates an inconsistency (e.g. moving away from menu slowly tends to hit same window, whereas moving away fast does not) // But we also need to not close the top-menu menu when moving over void. Perhaps we should extend the triangle check to a larger polygon. // (Remember to test this on BeginPopup("A")->BeginMenu("B") sequence which behaves slightly differently as B isn't a Child of A and hovering isn't shared.) - if (menu_is_open && !hovered && g.HoveredWindow == window && !moving_toward_child_menu && !g.NavDisableMouseHover && g.ActiveId == 0) + if (menu_is_open && !hovered && g.HoveredWindow == window && !moving_toward_child_menu && !g.NavHighlightItemUnderNav && g.ActiveId == 0) want_close = true; // Open @@ -7633,7 +8911,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) { want_open = want_open_nav_init = true; NavMoveRequestCancel(); - NavRestoreHighlightAfterMove(); + SetNavCursorVisibleAfterMove(); } } else @@ -7762,7 +9040,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut float w = label_size.x; window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f); ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); - PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); + PushStyleVarX(ImGuiStyleVar_ItemSpacing, style.ItemSpacing.x * 2.0f); pressed = Selectable("", selected, selectable_flags, ImVec2(w, 0.0f)); PopStyleVar(); if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) @@ -7788,6 +9066,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut if (shortcut_w > 0.0f) { PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); + LogSetNextTextDecoration("(", ")"); RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false); PopStyleColor(); } @@ -7832,8 +9111,10 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, // - TabBarCalcMaxTabWidth() [Internal] // - TabBarFindTabById() [Internal] // - TabBarFindTabByOrder() [Internal] +// - TabBarFindMostRecentlySelectedTabForActiveWindow() [Internal] // - TabBarGetCurrentTab() [Internal] // - TabBarGetTabName() [Internal] +// - TabBarAddTab() [Internal] // - TabBarRemoveTab() [Internal] // - TabBarCloseTab() [Internal] // - TabBarScrollClamp() [Internal] @@ -7923,7 +9204,9 @@ bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags) tab_bar->ID = id; tab_bar->SeparatorMinX = tab_bar->BarRect.Min.x - IM_TRUNC(window->WindowPadding.x * 0.5f); tab_bar->SeparatorMaxX = tab_bar->BarRect.Max.x + IM_TRUNC(window->WindowPadding.x * 0.5f); - return BeginTabBarEx(tab_bar, tab_bar_bb, flags | ImGuiTabBarFlags_IsFocused); + //if (g.NavWindow && IsWindowChildOf(g.NavWindow, window, false, false)) + flags |= ImGuiTabBarFlags_IsFocused; + return BeginTabBarEx(tab_bar, tab_bar_bb, flags); } bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags) @@ -7940,6 +9223,7 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG // Add to stack g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar)); g.CurrentTabBar = tab_bar; + tab_bar->Window = window; // Append with multiple BeginTabBar()/EndTabBar() pairs. tab_bar->BackupCursorPos = window->DC.CursorPos; @@ -7952,7 +9236,8 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG // Ensure correct ordering when toggling ImGuiTabBarFlags_Reorderable flag, or when a new tab was added while being not reorderable if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (tab_bar->TabsAddedNew && !(flags & ImGuiTabBarFlags_Reorderable))) - ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder); + if ((flags & ImGuiTabBarFlags_DockNode) == 0) // FIXME: TabBar with DockNode can now be hybrid + ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder); tab_bar->TabsAddedNew = false; // Flags @@ -7977,7 +9262,7 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG // Draw separator // (it would be misleading to draw this in EndTabBar() suggesting that it may be drawn over tabs, as tab bar are appendable) - const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive); + const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected); if (g.Style.TabBarBorderSize > 0.0f) { const float y = tab_bar->BarRect.Max.y; @@ -8234,6 +9519,10 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab_bar->VisibleTabId = tab_bar->SelectedTabId; tab_bar->VisibleTabWasSubmitted = false; + // CTRL+TAB can override visible tab temporarily + if (g.NavWindowingTarget != NULL && g.NavWindowingTarget->DockNode && g.NavWindowingTarget->DockNode->TabBar == tab_bar) + tab_bar->VisibleTabId = scroll_to_tab_id = g.NavWindowingTarget->TabId; + // Apply request requests if (scroll_to_tab_id != 0) TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections); @@ -8279,11 +9568,11 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) // Dockable windows uses Name/ID in the global namespace. Non-dockable items use the ID stack. static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window) { - IM_ASSERT(docked_window == NULL); // master branch only - IM_UNUSED(docked_window); - if (tab_bar->Flags & ImGuiTabBarFlags_DockNode) + if (docked_window != NULL) { - ImGuiID id = ImHashStr(label); + IM_UNUSED(tab_bar); + IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_DockNode); + ImGuiID id = docked_window->TabId; KeepAliveID(id); return id; } @@ -8317,6 +9606,20 @@ ImGuiTabItem* ImGui::TabBarFindTabByOrder(ImGuiTabBar* tab_bar, int order) return &tab_bar->Tabs[order]; } +// FIXME: See references to #2304 in TODO.txt +ImGuiTabItem* ImGui::TabBarFindMostRecentlySelectedTabForActiveWindow(ImGuiTabBar* tab_bar) +{ + ImGuiTabItem* most_recently_selected_tab = NULL; + for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; + if (most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) + if (tab->Window && tab->Window->WasActive) + most_recently_selected_tab = tab; + } + return most_recently_selected_tab; +} + ImGuiTabItem* ImGui::TabBarGetCurrentTab(ImGuiTabBar* tab_bar) { if (tab_bar->LastTabItemIdx < 0 || tab_bar->LastTabItemIdx >= tab_bar->Tabs.Size) @@ -8326,12 +9629,35 @@ ImGuiTabItem* ImGui::TabBarGetCurrentTab(ImGuiTabBar* tab_bar) const char* ImGui::TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) { + if (tab->Window) + return tab->Window->Name; if (tab->NameOffset == -1) return "N/A"; IM_ASSERT(tab->NameOffset < tab_bar->TabsNames.Buf.Size); return tab_bar->TabsNames.Buf.Data + tab->NameOffset; } +// The purpose of this call is to register tab in advance so we can control their order at the time they appear. +// Otherwise calling this is unnecessary as tabs are appending as needed by the BeginTabItem() function. +void ImGui::TabBarAddTab(ImGuiTabBar* tab_bar, ImGuiTabItemFlags tab_flags, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(TabBarFindTabByID(tab_bar, window->TabId) == NULL); + IM_ASSERT(g.CurrentTabBar != tab_bar); // Can't work while the tab bar is active as our tab doesn't have an X offset yet, in theory we could/should test something like (tab_bar->CurrFrameVisible < g.FrameCount) but we'd need to solve why triggers the commented early-out assert in BeginTabBarEx() (probably dock node going from implicit to explicit in same frame) + + if (!window->HasCloseButton) + tab_flags |= ImGuiTabItemFlags_NoCloseButton; // Set _NoCloseButton immediately because it will be used for first-frame width calculation. + + ImGuiTabItem new_tab; + new_tab.ID = window->TabId; + new_tab.Flags = tab_flags; + new_tab.LastFrameVisible = tab_bar->CurrFrameVisible; // Required so BeginTabBar() doesn't ditch the tab + if (new_tab.LastFrameVisible == -1) + new_tab.LastFrameVisible = g.FrameCount - 1; + new_tab.Window = window; // Required so tab bar layout can compute the tab width before tab submission + tab_bar->Tabs.push_back(new_tab); +} + // The *TabId fields are already set by the docking system _before_ the actual TabItem was created, so we clear them regardless. void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id) { @@ -8412,6 +9738,13 @@ void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) tab_bar->NextSelectedTabId = tab->ID; } +void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, const char* tab_name) +{ + IM_ASSERT((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0); // Only supported for manual/explicit tab bars + ImGuiID tab_id = TabBarCalcTabID(tab_bar, tab_name, NULL); + tab_bar->NextSelectedTabId = tab_id; +} + void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, int offset) { IM_ASSERT(offset != 0); @@ -8504,17 +9837,19 @@ static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar) PushStyleColor(ImGuiCol_Text, arrow_col); PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); const float backup_repeat_delay = g.IO.KeyRepeatDelay; const float backup_repeat_rate = g.IO.KeyRepeatRate; g.IO.KeyRepeatDelay = 0.250f; g.IO.KeyRepeatRate = 0.200f; float x = ImMax(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.x - scrolling_buttons_width); window->DC.CursorPos = ImVec2(x, tab_bar->BarRect.Min.y); - if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat)) + if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick)) select_dir = -1; window->DC.CursorPos = ImVec2(x + arrow_button_size.x, tab_bar->BarRect.Min.y); - if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat)) + if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick)) select_dir = +1; + PopItemFlag(); PopStyleColor(2); g.IO.KeyRepeatRate = backup_repeat_rate; g.IO.KeyRepeatDelay = backup_repeat_delay; @@ -8612,7 +9947,7 @@ bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags f IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!"); return false; } - IM_ASSERT(!(flags & ImGuiTabItemFlags_Button)); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead! + IM_ASSERT((flags & ImGuiTabItemFlags_Button) == 0); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead! bool ret = TabItemEx(tab_bar, label, p_open, flags, NULL); if (ret && !(flags & ImGuiTabItemFlags_NoPushId)) @@ -8708,7 +10043,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // Calculate tab contents size ImVec2 size = TabItemCalcSize(label, (p_open != NULL) || (flags & ImGuiTabItemFlags_UnsavedDocument)); tab->RequestedWidth = -1.0f; - if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasWidth) + if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasWidth) size.x = tab->RequestedWidth = g.NextItemData.Width; if (tab_is_new) tab->Width = ImMax(1.0f, size.x); @@ -8722,11 +10057,14 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0; tab->LastFrameVisible = g.FrameCount; tab->Flags = flags; + tab->Window = docked_window; // Append name _WITH_ the zero-terminator + // (regular tabs are permitted in a DockNode tab bar, but window tabs not permitted in a non-DockNode tab bar) if (docked_window != NULL) { - IM_ASSERT(docked_window == NULL); // master branch only + IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_DockNode); + tab->NameOffset = -1; } else { @@ -8751,7 +10089,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, tab_bar->VisibleTabWasSubmitted = true; // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches - if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing) + if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing && docked_window == NULL) if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs)) tab_contents_visible = true; @@ -8799,30 +10137,82 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, } // Click to Select a tab - // Allow the close button to overlap ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowOverlap); - if (g.DragDropActive) + if (g.DragDropActive && !g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW)) // FIXME: May be an opt-in property of the payload to disable this button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); if (pressed && !is_tab_button) TabBarQueueFocus(tab_bar, tab); - // Drag and drop: re-order tabs - if (held && !tab_appearing && IsMouseDragging(0)) + // Transfer active id window so the active id is not owned by the dock host (as StartMouseMovingWindow() + // will only do it on the drag). This allows FocusWindow() to be more conservative in how it clears active id. + if (held && docked_window && g.ActiveId == id && g.ActiveIdIsJustActivated) + g.ActiveIdWindow = docked_window; + + // Drag and drop a single floating window node moves it + ImGuiDockNode* node = docked_window ? docked_window->DockNode : NULL; + const bool single_floating_window_node = node && node->IsFloatingNode() && (node->Windows.Size == 1); + if (held && single_floating_window_node && IsMouseDragging(0, 0.0f)) { - if (!g.DragDropActive && (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)) + // Move + StartMouseMovingWindow(docked_window); + } + else if (held && !tab_appearing && IsMouseDragging(0)) + { + // Drag and drop: re-order tabs + int drag_dir = 0; + float drag_distance_from_edge_x = 0.0f; + if (!g.DragDropActive && ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (docked_window != NULL))) { // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x) { + drag_dir = -1; + drag_distance_from_edge_x = bb.Min.x - g.IO.MousePos.x; TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos); } else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x) { + drag_dir = +1; + drag_distance_from_edge_x = g.IO.MousePos.x - bb.Max.x; TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos); } } + + // Extract a Dockable window out of it's tab bar + const bool can_undock = docked_window != NULL && !(docked_window->Flags & ImGuiWindowFlags_NoMove) && !(node->MergedFlags & ImGuiDockNodeFlags_NoUndocking); + if (can_undock) + { + // We use a variable threshold to distinguish dragging tabs within a tab bar and extracting them out of the tab bar + bool undocking_tab = (g.DragDropActive && g.DragDropPayload.SourceId == id); + if (!undocking_tab) //&& (!g.IO.ConfigDockingWithShift || g.IO.KeyShift) + { + float threshold_base = g.FontSize; + float threshold_x = (threshold_base * 2.2f); + float threshold_y = (threshold_base * 1.5f) + ImClamp((ImFabs(g.IO.MouseDragMaxDistanceAbs[0].x) - threshold_base * 2.0f) * 0.20f, 0.0f, threshold_base * 4.0f); + //GetForegroundDrawList()->AddRect(ImVec2(bb.Min.x - threshold_x, bb.Min.y - threshold_y), ImVec2(bb.Max.x + threshold_x, bb.Max.y + threshold_y), IM_COL32_WHITE); // [DEBUG] + + float distance_from_edge_y = ImMax(bb.Min.y - g.IO.MousePos.y, g.IO.MousePos.y - bb.Max.y); + if (distance_from_edge_y >= threshold_y) + undocking_tab = true; + if (drag_distance_from_edge_x > threshold_x) + if ((drag_dir < 0 && TabBarGetTabOrder(tab_bar, tab) == 0) || (drag_dir > 0 && TabBarGetTabOrder(tab_bar, tab) == tab_bar->Tabs.Size - 1)) + undocking_tab = true; + } + + if (undocking_tab) + { + // Undock + // FIXME: refactor to share more code with e.g. StartMouseMovingWindow + DockContextQueueUndockWindow(&g, docked_window); + g.MovingWindow = docked_window; + SetActiveID(g.MovingWindow->MoveId, g.MovingWindow); + g.ActiveIdClickOffset -= g.MovingWindow->Pos - bb.Min; + g.ActiveIdNoClearOnFocusLoss = true; + SetActiveIdUsingAllKeyboardKeys(); + } + } } #if 0 @@ -8837,20 +10227,28 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // Render tab shape ImDrawList* display_draw_list = window->DrawList; - const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabUnfocused)); + const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabDimmed)); TabItemBackground(display_draw_list, bb, flags, tab_col); - RenderNavHighlight(bb, id); + if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f) + { + float x_offset = IM_TRUNC(0.4f * style.TabRounding); + if (x_offset < 2.0f * g.CurrentDpiScale) + x_offset = 0.0f; + float y_offset = 1.0f * g.CurrentDpiScale; + display_draw_list->AddLine(bb.GetTL() + ImVec2(x_offset, y_offset), bb.GetTR() + ImVec2(-x_offset, y_offset), GetColorU32(tab_bar_focused ? ImGuiCol_TabSelectedOverline : ImGuiCol_TabDimmedSelectedOverline), style.TabBarOverlineSize); + } + RenderNavCursor(bb, id); // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget. const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); - if (hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)) && !is_tab_button) + if (tab_bar->SelectedTabId != tab->ID && hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)) && !is_tab_button) TabBarQueueFocus(tab_bar, tab); if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton) flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton; // Render tab label, process close button - const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, id) : 0; + const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, docked_window ? docked_window->ID : id) : 0; bool just_closed; bool text_clipped; TabItemLabelAndCloseButton(display_draw_list, bb, tab_just_unsaved ? (flags & ~ImGuiTabItemFlags_UnsavedDocument) : flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped); @@ -8860,6 +10258,11 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, TabBarCloseTab(tab_bar, tab); } + // Forward Hovered state so IsItemHovered() after Begin() can work (even though we are technically hovering our parent) + // That state is copied to window->DockTabItemStatusFlags by our caller. + if (docked_window && (hovered || g.HoveredId == close_button_id)) + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; + // Restore main window position so user can draw there if (want_clip_rect) PopClipRect(); @@ -8894,6 +10297,16 @@ void ImGui::SetTabItemClosed(const char* label) if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id)) tab->WantClose = true; // Will be processed by next call to TabBarLayout() } + else if (ImGuiWindow* window = FindWindowByName(label)) + { + if (window->DockIsActive) + if (ImGuiDockNode* node = window->DockNode) + { + ImGuiID tab_id = TabBarCalcTabID(node->TabBar, label, window); + TabBarRemoveTab(node->TabBar, tab_id); + window->DockTabWantClose = true; + } + } } ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button_or_unsaved_marker) @@ -8908,10 +10321,9 @@ ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button_or_unsave return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y); } -ImVec2 ImGui::TabItemCalcSize(ImGuiWindow*) +ImVec2 ImGui::TabItemCalcSize(ImGuiWindow* window) { - IM_ASSERT(0); // This function exists to facilitate merge with 'docking' branch. - return ImVec2(0.0f, 0.0f); + return TabItemCalcSize(window->Name, window->HasCloseButton || (window->Flags & ImGuiWindowFlags_UnsavedDocument)); } void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col) @@ -9016,6 +10428,7 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, text_ellipsis_clip_bb.Max.x -= unsaved_marker_visible ? (button_sz * 0.80f) : 0.0f; ellipsis_max_x = text_pixel_clip_bb.Max.x; } + LogSetNextTextDecoration("/", "\\"); RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size); #if 0 diff --git a/ext/imgui/imstb_textedit.h b/ext/imgui/imstb_textedit.h index 783054ab953e..b7a761c85381 100644 --- a/ext/imgui/imstb_textedit.h +++ b/ext/imgui/imstb_textedit.h @@ -3,6 +3,8 @@ // Those changes would need to be pushed into nothings/stb: // - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321) // - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000 + #6783) +// - Added name to struct or it may be forward declared in our code. +// - Added UTF-8 support (see https://github.com/nothings/stb/issues/188 + https://github.com/ocornut/imgui/pull/7925) // Grep for [DEAR IMGUI] to find the changes. // - Also renamed macros used or defined outside of IMSTB_TEXTEDIT_IMPLEMENTATION block from STB_TEXTEDIT_* to IMSTB_TEXTEDIT_* @@ -209,6 +211,7 @@ // int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) // int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) // void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key) +// void stb_textedit_text(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int text_len) // // Each of these functions potentially updates the string and updates the // state. @@ -243,7 +246,12 @@ // various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit // set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is // clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to -// anything other type you wante before including. +// anything other type you want before including. +// if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are +// transformed into text and stb_textedit_text() is automatically called. +// +// text: [DEAR IMGUI] added 2024-09 +// call this to text inputs sent to the textfield. // // // When rendering, you can read the cursor position and selection state from @@ -318,7 +326,7 @@ typedef struct int undo_char_point, redo_char_point; } StbUndoState; -typedef struct +typedef struct STB_TexteditState { ///////////////////// // @@ -438,13 +446,13 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y) if (x < r.x1) { // search characters in row for one that straddles 'x' prev_x = r.x0; - for (k=0; k < r.num_chars; ++k) { + for (k=0; k < r.num_chars; k = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k) - i) { float w = STB_TEXTEDIT_GETWIDTH(str, i, k); if (x < prev_x+w) { if (x < prev_x+w/2) return k+i; else - return k+i+1; + return IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k); } prev_x += w; } @@ -563,7 +571,7 @@ static void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING // now scan to find xpos find->x = r.x0; - for (i=0; first+i < n; ++i) + for (i=0; first+i < n; i = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, first + i) - first) find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); } @@ -640,6 +648,17 @@ static void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditSt } } +// [DEAR IMGUI] +// Functions must be implemented for UTF8 support +// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit. +// There is not necessarily a '[DEAR IMGUI]' at the usage sites. +#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX +#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx) (idx - 1) +#endif +#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX +#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx) (idx + 1) +#endif + #ifdef STB_TEXTEDIT_IS_SPACE static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx ) { @@ -720,36 +739,44 @@ static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditS #define STB_TEXTEDIT_KEYTYPE int #endif +// [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility. +static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len) +{ + // can't add newline in single-line mode + if (text[0] == '\n' && state->single_line) + return; + + if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { + stb_text_makeundo_replace(str, state, state->cursor, 1, 1); + STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) { + state->cursor += text_len; + state->has_preferred_x = 0; + } + } + else { + stb_textedit_delete_selection(str, state); // implicitly clamps + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) { + stb_text_makeundo_insert(state, state->cursor, text_len); + state->cursor += text_len; + state->has_preferred_x = 0; + } + } +} + // API key: process a keyboard input static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key) { retry: switch (key) { default: { +#ifdef STB_TEXTEDIT_KEYTOTEXT int c = STB_TEXTEDIT_KEYTOTEXT(key); if (c > 0) { - IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE) c; - - // can't add newline in single-line mode - if (c == '\n' && state->single_line) - break; - - if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { - stb_text_makeundo_replace(str, state, state->cursor, 1, 1); - STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); - if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { - ++state->cursor; - state->has_preferred_x = 0; - } - } else { - stb_textedit_delete_selection(str,state); // implicitly clamps - if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { - stb_text_makeundo_insert(state, state->cursor, 1); - ++state->cursor; - state->has_preferred_x = 0; - } - } + IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c; + stb_textedit_text(str, state, &ch, 1); } +#endif break; } @@ -775,7 +802,7 @@ static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *stat stb_textedit_move_to_first(state); else if (state->cursor > 0) - --state->cursor; + state->cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor); state->has_preferred_x = 0; break; @@ -784,7 +811,7 @@ static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *stat if (STB_TEXT_HAS_SELECTION(state)) stb_textedit_move_to_last(str, state); else - ++state->cursor; + state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); stb_textedit_clamp(str, state); state->has_preferred_x = 0; break; @@ -794,7 +821,7 @@ static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *stat stb_textedit_prep_selection_at_cursor(state); // move selection left if (state->select_end > 0) - --state->select_end; + state->select_end = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->select_end); state->cursor = state->select_end; state->has_preferred_x = 0; break; @@ -844,7 +871,7 @@ static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *stat case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: stb_textedit_prep_selection_at_cursor(state); // move selection right - ++state->select_end; + state->select_end = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->select_end); stb_textedit_clamp(str, state); state->cursor = state->select_end; state->has_preferred_x = 0; @@ -900,7 +927,7 @@ static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *stat x += dx; if (x > goal_x) break; - ++state->cursor; + state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); } stb_textedit_clamp(str, state); @@ -962,7 +989,7 @@ static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *stat x += dx; if (x > goal_x) break; - ++state->cursor; + state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); } stb_textedit_clamp(str, state); @@ -990,7 +1017,7 @@ static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *stat else { int n = STB_TEXTEDIT_STRINGLEN(str); if (state->cursor < n) - stb_textedit_delete(str, state, state->cursor, 1); + stb_textedit_delete(str, state, state->cursor, IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor) - state->cursor); } state->has_preferred_x = 0; break; @@ -1002,8 +1029,9 @@ static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *stat else { stb_textedit_clamp(str, state); if (state->cursor > 0) { - stb_textedit_delete(str, state, state->cursor-1, 1); - --state->cursor; + int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor); + stb_textedit_delete(str, state, prev, state->cursor - prev); + state->cursor = prev; } } state->has_preferred_x = 0; From 60e244291a94bb66ef84aef8ed843995a6b60e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 12 Dec 2024 23:01:40 +0100 Subject: [PATCH 30/58] Enable a dockspace, so you can dock windows to the sides of the screen --- GPU/Common/TextureCacheCommon.cpp | 2 +- UI/EmuScreen.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index d10712d11d3b..84cbf9067954 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -3067,7 +3067,7 @@ void TextureCacheCommon::DrawImGuiDebug(uint64_t &selectedTextureId) const { ImVec2 avail = ImGui::GetContentRegionAvail(); auto &style = ImGui::GetStyle(); ImGui::BeginChild("left", ImVec2(140.0f, 0.0f), ImGuiChildFlags_ResizeX); - float window_visible_x2 = ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x; + float window_visible_x2 = ImGui::GetCursorScreenPos().x + ImGui::GetContentRegionAvail().x; // Global texture stats int replacementStateCounts[(int)ReplacementState::COUNT]{}; diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index e8d809c0da32..6725eda50499 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -1686,6 +1686,8 @@ void EmuScreen::renderImDebugger() { io.AddKeyEvent(ImGuiMod_Alt, keyAltLeft_ || keyAltRight_); // io.AddKeyEvent(ImGuiMod_Super, e.key.super); + ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode); + imDebugger_->Frame(currentDebugMIPS, gpuDebug); ImGui::Render(); From 5aeef924d27eec8759abe9622d5e8a8d8cbe46a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 12 Dec 2024 23:35:58 +0100 Subject: [PATCH 31/58] Implement vertex preview in the new Ge debugger --- GPU/Debugger/State.cpp | 4 +- GPU/Debugger/State.h | 2 +- UI/ImDebugger/ImDebugger.cpp | 4 +- UI/ImDebugger/ImDebugger.h | 3 ++ UI/ImDebugger/ImGe.cpp | 71 +++++++++++++++++++++++++++- UI/ImDebugger/ImGe.h | 12 ++++- Windows/GEDebugger/VertexPreview.cpp | 6 ++- 7 files changed, 93 insertions(+), 9 deletions(-) diff --git a/GPU/Debugger/State.cpp b/GPU/Debugger/State.cpp index 54d5be6062ae..4398d79768f4 100644 --- a/GPU/Debugger/State.cpp +++ b/GPU/Debugger/State.cpp @@ -831,7 +831,7 @@ static void ExpandSpline(int &count, int op, const std::vector &si FreeAlignedMemory(cpoints.col); } -bool GetPrimPreview(u32 op, int which, GEPrimitiveType &prim, std::vector &vertices, std::vector &indices, int &count) { +bool GetPrimPreview(u32 op, GEPrimitiveType &prim, std::vector &vertices, std::vector &indices, int &count) { u32 prim_type = GE_PRIM_INVALID; int count_u = 0; int count_v = 0; @@ -857,7 +857,7 @@ bool GetPrimPreview(u32 op, int which, GEPrimitiveType &prim, std::vector &vertices, std::vector &indices, int &count); +bool GetPrimPreview(u32 op, GEPrimitiveType &prim, std::vector &vertices, std::vector &indices, int &count); void DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]); void DescribePixelRGBA(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]); diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index b4b4939c5def..4acde09d8520 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -902,14 +902,14 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu if (lastCpuStepCount_ != Core_GetSteppingCounter()) { lastCpuStepCount_ = Core_GetSteppingCounter(); - disasm_.View().NotifyStep(); + disasm_.NotifyStep(); } if (lastGpuStepCount_ != GPUStepping::GetSteppingCounter()) { // A GPU step has happened since last time. This means that we should re-center the cursor. // Snapshot(); lastGpuStepCount_ = GPUStepping::GetSteppingCounter(); - geDebugger_.View().NotifyStep(); + geDebugger_.NotifyStep(); } ImControl control{}; diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h index 1af95f0169f4..9ffb9ce7bbba 100644 --- a/UI/ImDebugger/ImDebugger.h +++ b/UI/ImDebugger/ImDebugger.h @@ -36,6 +36,9 @@ class ImDisasmWindow { ImDisasmView &View() { return disasmView_; } + void NotifyStep() { + disasmView_.NotifyStep(); + } void DirtySymbolMap() { symsDirty_ = true; } diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index d5e73da10e22..71aac55dcbef 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -367,8 +367,26 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa ImGui::SameLine(); + u32 op = 0; + DisplayList list; + if (gpuDebug->GetCurrentDisplayList(list)) { + op = Memory::Read_U32(list.pc); + bool isOnPrim = (op >> 24) == GE_CMD_PRIM; + if (isOnPrim) { + if (reloadPreview_) { + GetPrimPreview(op, previewPrim_, previewVertices_, previewIndices_, previewCount_); + reloadPreview_ = false; + } + } else { + previewCount_ = 0; + } + } + ImGui::BeginChild("texture/fb view"); // Leave room for 1 line below us + + ImDrawList *drawList = ImGui::GetWindowDrawList(); + if (coreState == CORE_STEPPING_GE) { FramebufferManagerCommon *fbman = gpuDebug->GetFramebufferManagerCommon(); u32 fbptr = gstate.getFrameBufAddress(); @@ -384,8 +402,31 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa if (ImGui::BeginTabBar("aspects")) { if (ImGui::BeginTabItem("Color")) { ImTextureID texId = ImGui_ImplThin3d_AddFBAsTextureTemp(vfb->fbo, Draw::FB_COLOR_BIT, ImGuiPipeline::TexturedOpaque); + const ImVec2 p0 = ImGui::GetCursorScreenPos(); ImGui::Image(texId, ImVec2(vfb->width, vfb->height)); - // TODO: Draw vertex preview on top! + // Draw vertex preview on top! + if (previewCount_) { + switch (previewPrim_) { + case GE_PRIM_TRIANGLES: + case GE_PRIM_RECTANGLES: + { + for (int i = 0; i < previewCount_ - 2; i += 3) { + const auto &v1 = previewIndices_.empty() ? previewVertices_[i] : previewVertices_[previewIndices_[i]]; + const auto &v2 = previewIndices_.empty() ? previewVertices_[i + 1] : previewVertices_[previewIndices_[i + 1]]; + const auto &v3 = previewIndices_.empty() ? previewVertices_[i + 2] : previewVertices_[previewIndices_[i + 2]]; + drawList->AddTriangleFilled( + ImVec2(p0.x + v1.x, p0.y + v1.y), + ImVec2(p0.x + v2.x, p0.y + v2.y), + ImVec2(p0.x + v3.x, p0.y + v3.y), ImColor(0x600000FF)); + } + break; + } + default: + // TODO: Support lines etc. + break; + } + } + ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Depth")) { @@ -415,7 +456,33 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa void *nativeView = texcache->GetNativeTextureView(tex, true); ImTextureID texId = ImGui_ImplThin3d_AddNativeTextureTemp(nativeView); - ImGui::Image(texId, ImVec2(128, 128)); + const ImVec2 p0 = ImGui::GetCursorScreenPos(); + float texW = dimWidth(tex->dim); + float texH = dimHeight(tex->dim); + ImGui::Image(texId, ImVec2(texW, texH)); + + // Draw UVs of vertex preview on top of the texture! + if (previewCount_) { + switch (previewPrim_) { + case GE_PRIM_TRIANGLES: + case GE_PRIM_RECTANGLES: + { + for (int i = 0; i < previewCount_ - 2; i += 3) { + const auto &v1 = previewIndices_.empty() ? previewVertices_[i] : previewVertices_[previewIndices_[i]]; + const auto &v2 = previewIndices_.empty() ? previewVertices_[i + 1] : previewVertices_[previewIndices_[i + 1]]; + const auto &v3 = previewIndices_.empty() ? previewVertices_[i + 2] : previewVertices_[previewIndices_[i + 2]]; + drawList->AddTriangleFilled( + ImVec2(p0.x + v1.u * texW, p0.y + v1.v * texH), + ImVec2(p0.x + v2.u * texW, p0.y + v2.v * texH), + ImVec2(p0.x + v3.u * texW, p0.y + v3.v * texH), ImColor(0x600000FF)); + } + break; + } + default: + // TODO: Support lines etc. + break; + } + } // Let's display the current CLUT. diff --git a/UI/ImDebugger/ImGe.h b/UI/ImDebugger/ImGe.h index f4cadbda7548..fda9d3e2bd52 100644 --- a/UI/ImDebugger/ImGe.h +++ b/UI/ImDebugger/ImGe.h @@ -1,5 +1,7 @@ #pragma once +#include "GPU/Common/GPUDebugInterface.h" + // GE-related windows of the ImDebugger struct ImConfig; @@ -7,7 +9,6 @@ struct ImControl; class FramebufferManagerCommon; class TextureCacheCommon; -class GPUDebugInterface; void DrawFramebuffersWindow(ImConfig &cfg, FramebufferManagerCommon *framebufferManager); void DrawTexturesWindow(ImConfig &cfg, TextureCacheCommon *textureCache); @@ -57,8 +58,17 @@ class ImGeDebuggerWindow { const char *Title() const { return "GE Debugger"; } + void NotifyStep() { + reloadPreview_ = true; + disasmView_.NotifyStep(); + } private: ImGeDisasmView disasmView_; int showBannerInFrames_ = 0; + bool reloadPreview_ = false; + GEPrimitiveType previewPrim_; + std::vector previewIndices_; + std::vector previewVertices_; + int previewCount_ = 0; }; diff --git a/Windows/GEDebugger/VertexPreview.cpp b/Windows/GEDebugger/VertexPreview.cpp index 486c989321cf..eaf4fdc503b6 100644 --- a/Windows/GEDebugger/VertexPreview.cpp +++ b/Windows/GEDebugger/VertexPreview.cpp @@ -89,12 +89,16 @@ u32 CGEDebugger::PrimPreviewOp() { void CGEDebugger::UpdatePrimPreview(u32 op, int which) { which &= previewsEnabled_; + if (which == 0) { + return; + } + static std::vector vertices; static std::vector indices; int count = 0; GEPrimitiveType prim; - if (!GetPrimPreview(op, which, prim, vertices, indices, count)) { + if (!GetPrimPreview(op, prim, vertices, indices, count)) { return; } From c4902db2961b8c13001250fdf788d064232ebe5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 13 Dec 2024 12:30:54 +0100 Subject: [PATCH 32/58] Fix vertex previews for triangle fans and strips, and lines. --- UI/ImDebugger/ImGe.cpp | 157 ++++++++++++++++++++++++------------ UI/ImDebugger/ImMemView.cpp | 5 +- 2 files changed, 106 insertions(+), 56 deletions(-) diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index 71aac55dcbef..096420cd54ed 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -254,6 +254,92 @@ static const char *DLStateToString(DisplayListState state) { } } +static void DrawPreviewPrimitive(ImDrawList *drawList, ImVec2 p0, GEPrimitiveType prim, const std::vector &indices, const std::vector &verts, int count, bool uvToPos, float sx = 1.0f, float sy = 1.0f) { + if (count) { + auto x = [sx, uvToPos](const GPUDebugVertex &vert) { + return sx * (uvToPos ? vert.u : vert.x); + }; + auto y = [sy, uvToPos](const GPUDebugVertex &vert) { + return sy * (uvToPos ? vert.v : vert.y); + }; + + // TODO: Maybe not the best idea to use the heavy AddTriangleFilled API instead of just adding raw triangles. + switch (prim) { + case GE_PRIM_TRIANGLES: + case GE_PRIM_RECTANGLES: + { + for (int i = 0; i < count - 2; i += 3) { + const auto &v1 = indices.empty() ? verts[i] : verts[indices[i]]; + const auto &v2 = indices.empty() ? verts[i + 1] : verts[indices[i + 1]]; + const auto &v3 = indices.empty() ? verts[i + 2] : verts[indices[i + 2]]; + drawList->AddTriangleFilled( + ImVec2(p0.x + x(v1), p0.y + y(v1)), + ImVec2(p0.x + x(v2), p0.y + y(v2)), + ImVec2(p0.x + x(v3), p0.y + y(v3)), ImColor(0x600000FF)); + } + break; + } + case GE_PRIM_TRIANGLE_FAN: + { + for (int i = 0; i < count - 2; i++) { + const auto &v1 = indices.empty() ? verts[0] : verts[indices[0]]; + const auto &v2 = indices.empty() ? verts[i + 1] : verts[indices[i + 1]]; + const auto &v3 = indices.empty() ? verts[i + 2] : verts[indices[i + 2]]; + drawList->AddTriangleFilled( + ImVec2(p0.x + x(v1), p0.y + y(v1)), + ImVec2(p0.x + x(v2), p0.y + y(v2)), + ImVec2(p0.x + x(v3), p0.y + y(v3)), ImColor(0x600000FF)); + } + break; + } + case GE_PRIM_TRIANGLE_STRIP: + { + int t = 2; + for (int i = 0; i < count - 2; i++) { + int i0 = i; + int i1 = i + t; + int i2 = i + (t ^ 3); + const auto &v1 = indices.empty() ? verts[i0] : verts[indices[i0]]; + const auto &v2 = indices.empty() ? verts[i1] : verts[indices[i1]]; + const auto &v3 = indices.empty() ? verts[i2] : verts[indices[i2]]; + drawList->AddTriangleFilled( + ImVec2(p0.x + x(v1), p0.y + y(v1)), + ImVec2(p0.x + x(v2), p0.y + y(v2)), + ImVec2(p0.x + x(v3), p0.y + y(v3)), ImColor(0x600000FF)); + t ^= 3; + } + break; + } + case GE_PRIM_LINES: + { + for (int i = 0; i < count - 1; i += 2) { + const auto &v1 = indices.empty() ? verts[i] : verts[indices[i]]; + const auto &v2 = indices.empty() ? verts[i + 1] : verts[indices[i + 1]]; + drawList->AddLine( + ImVec2(p0.x + x(v1), p0.y + y(v1)), + ImVec2(p0.x + x(v2), p0.y + y(v2)), ImColor(0x600000FF)); + } + break; + } + case GE_PRIM_LINE_STRIP: + { + for (int i = 0; i < count - 2; i++) { + const auto &v1 = indices.empty() ? verts[i] : verts[indices[i]]; + const auto &v2 = indices.empty() ? verts[i + 1] : verts[indices[i + 1]]; + drawList->AddLine( + ImVec2(p0.x + x(v1), p0.y + y(v1)), + ImVec2(p0.x + x(v2), p0.y + y(v2)), ImColor(0x600000FF)); + } + break; + } + default: + // TODO: Support lines etc. + break; + } + } +} + + void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterface *gpuDebug) { ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); if (!ImGui::Begin(Title(), &cfg.geDebuggerOpen)) { @@ -404,28 +490,9 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa ImTextureID texId = ImGui_ImplThin3d_AddFBAsTextureTemp(vfb->fbo, Draw::FB_COLOR_BIT, ImGuiPipeline::TexturedOpaque); const ImVec2 p0 = ImGui::GetCursorScreenPos(); ImGui::Image(texId, ImVec2(vfb->width, vfb->height)); + // Draw vertex preview on top! - if (previewCount_) { - switch (previewPrim_) { - case GE_PRIM_TRIANGLES: - case GE_PRIM_RECTANGLES: - { - for (int i = 0; i < previewCount_ - 2; i += 3) { - const auto &v1 = previewIndices_.empty() ? previewVertices_[i] : previewVertices_[previewIndices_[i]]; - const auto &v2 = previewIndices_.empty() ? previewVertices_[i + 1] : previewVertices_[previewIndices_[i + 1]]; - const auto &v3 = previewIndices_.empty() ? previewVertices_[i + 2] : previewVertices_[previewIndices_[i + 2]]; - drawList->AddTriangleFilled( - ImVec2(p0.x + v1.x, p0.y + v1.y), - ImVec2(p0.x + v2.x, p0.y + v2.y), - ImVec2(p0.x + v3.x, p0.y + v3.y), ImColor(0x600000FF)); - } - break; - } - default: - // TODO: Support lines etc. - break; - } - } + DrawPreviewPrimitive(drawList, p0, previewPrim_, previewIndices_, previewVertices_, previewCount_, false); ImGui::EndTabItem(); } @@ -447,41 +514,25 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa ImGui::Text("%dx%d (emulated: %dx%d)", vfb->width, vfb->height, vfb->bufferWidth, vfb->bufferHeight); } - ImGui::Text("Texture: "); TextureCacheCommon *texcache = gpuDebug->GetTextureCacheCommon(); TexCacheEntry *tex = texcache->SetTexture(); - texcache->ApplyTexture(); - - void *nativeView = texcache->GetNativeTextureView(tex, true); - ImTextureID texId = ImGui_ImplThin3d_AddNativeTextureTemp(nativeView); - - const ImVec2 p0 = ImGui::GetCursorScreenPos(); - float texW = dimWidth(tex->dim); - float texH = dimHeight(tex->dim); - ImGui::Image(texId, ImVec2(texW, texH)); - - // Draw UVs of vertex preview on top of the texture! - if (previewCount_) { - switch (previewPrim_) { - case GE_PRIM_TRIANGLES: - case GE_PRIM_RECTANGLES: - { - for (int i = 0; i < previewCount_ - 2; i += 3) { - const auto &v1 = previewIndices_.empty() ? previewVertices_[i] : previewVertices_[previewIndices_[i]]; - const auto &v2 = previewIndices_.empty() ? previewVertices_[i + 1] : previewVertices_[previewIndices_[i + 1]]; - const auto &v3 = previewIndices_.empty() ? previewVertices_[i + 2] : previewVertices_[previewIndices_[i + 2]]; - drawList->AddTriangleFilled( - ImVec2(p0.x + v1.u * texW, p0.y + v1.v * texH), - ImVec2(p0.x + v2.u * texW, p0.y + v2.v * texH), - ImVec2(p0.x + v3.u * texW, p0.y + v3.v * texH), ImColor(0x600000FF)); - } - break; - } - default: - // TODO: Support lines etc. - break; - } + if (tex) { + ImGui::Text("Texture: "); + texcache->ApplyTexture(); + + void *nativeView = texcache->GetNativeTextureView(tex, true); + ImTextureID texId = ImGui_ImplThin3d_AddNativeTextureTemp(nativeView); + + const ImVec2 p0 = ImGui::GetCursorScreenPos(); + float texW = dimWidth(tex->dim); + float texH = dimHeight(tex->dim); + ImGui::Image(texId, ImVec2(texW, texH)); + + DrawPreviewPrimitive(drawList, p0, previewPrim_, previewIndices_, previewVertices_, previewCount_, true, texW, texH); + } else { + ImGui::Text("(no valid texture bound)"); + // TODO: List some of the texture params here. } // Let's display the current CLUT. diff --git a/UI/ImDebugger/ImMemView.cpp b/UI/ImDebugger/ImMemView.cpp index b3eb3e58243a..99f2d8e134c4 100644 --- a/UI/ImDebugger/ImMemView.cpp +++ b/UI/ImDebugger/ImMemView.cpp @@ -529,12 +529,11 @@ void ImMemView::onMouseMove(float x, float y, int button) { void ImMemView::updateStatusBarText() { std::vector memRangeInfo = FindMemInfoByFlag(highlightFlags_, curAddress_, 1); - char text[512]; - snprintf(text, sizeof(text), "%08X", curAddress_); + snprintf(text, sizeof(text), "%08x", curAddress_); // There should only be one. for (MemBlockInfo info : memRangeInfo) { - snprintf(text, sizeof(text), "%08X - %s %08X-%08X (at PC %08X / %lld ticks)", curAddress_, info.tag.c_str(), info.start, info.start + info.size, info.pc, info.ticks); + snprintf(text, sizeof(text), "%08x - %s %08x-%08x (alloc'd at PC %08x / %lld ticks)", curAddress_, info.tag.c_str(), info.start, info.start + info.size, info.pc, info.ticks); } statusMessage_ = text; } From 198dae2ede276a28a060e73a274aaf2f23cb327e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 13 Dec 2024 12:49:03 +0100 Subject: [PATCH 33/58] Add clipping --- UI/ImDebugger/ImGe.cpp | 51 +++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index 096420cd54ed..bfa8769d7b02 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -470,7 +470,6 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa ImGui::BeginChild("texture/fb view"); // Leave room for 1 line below us - ImDrawList *drawList = ImGui::GetWindowDrawList(); if (coreState == CORE_STEPPING_GE) { @@ -489,11 +488,18 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa if (ImGui::BeginTabItem("Color")) { ImTextureID texId = ImGui_ImplThin3d_AddFBAsTextureTemp(vfb->fbo, Draw::FB_COLOR_BIT, ImGuiPipeline::TexturedOpaque); const ImVec2 p0 = ImGui::GetCursorScreenPos(); + const ImVec2 p1 = ImVec2(p0.x + vfb->width, p0.y + vfb->height); + + // Draw border and background color + drawList->PushClipRect(p0, p1, true); + ImGui::Image(texId, ImVec2(vfb->width, vfb->height)); // Draw vertex preview on top! DrawPreviewPrimitive(drawList, p0, previewPrim_, previewIndices_, previewVertices_, previewCount_, false); + drawList->PopClipRect(); + ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Depth")) { @@ -514,25 +520,38 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa ImGui::Text("%dx%d (emulated: %dx%d)", vfb->width, vfb->height, vfb->bufferWidth, vfb->bufferHeight); } + if (gstate.isModeClear()) { + ImGui::Text("(clear mode - texturing not used)"); + } else if (!gstate.isTextureMapEnabled()) { + ImGui::Text("(texturing not enabled"); + } else { + TextureCacheCommon *texcache = gpuDebug->GetTextureCacheCommon(); + TexCacheEntry *tex = texcache->SetTexture(); + if (tex) { + ImGui::Text("Texture: "); + texcache->ApplyTexture(); + + void *nativeView = texcache->GetNativeTextureView(tex, true); + ImTextureID texId = ImGui_ImplThin3d_AddNativeTextureTemp(nativeView); - TextureCacheCommon *texcache = gpuDebug->GetTextureCacheCommon(); - TexCacheEntry *tex = texcache->SetTexture(); - if (tex) { - ImGui::Text("Texture: "); - texcache->ApplyTexture(); + float texW = dimWidth(tex->dim); + float texH = dimHeight(tex->dim); - void *nativeView = texcache->GetNativeTextureView(tex, true); - ImTextureID texId = ImGui_ImplThin3d_AddNativeTextureTemp(nativeView); + const ImVec2 p0 = ImGui::GetCursorScreenPos(); + const ImVec2 sz = ImGui::GetContentRegionAvail(); + const ImVec2 p1 = ImVec2(p0.x + texW, p0.y + texH); - const ImVec2 p0 = ImGui::GetCursorScreenPos(); - float texW = dimWidth(tex->dim); - float texH = dimHeight(tex->dim); - ImGui::Image(texId, ImVec2(texW, texH)); + // Draw border and background color + drawList->PushClipRect(p0, p1, true); - DrawPreviewPrimitive(drawList, p0, previewPrim_, previewIndices_, previewVertices_, previewCount_, true, texW, texH); - } else { - ImGui::Text("(no valid texture bound)"); - // TODO: List some of the texture params here. + ImGui::Image(texId, ImVec2(texW, texH)); + DrawPreviewPrimitive(drawList, p0, previewPrim_, previewIndices_, previewVertices_, previewCount_, true, texW, texH); + + drawList->PopClipRect(); + } else { + ImGui::Text("(no valid texture bound)"); + // TODO: List some of the texture params here. + } } // Let's display the current CLUT. From 6a375b0ddc08895457415f98121ae6ab6e8e4c23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 13 Dec 2024 13:47:42 +0100 Subject: [PATCH 34/58] TextureReplacer: Load the ini, even if just saving. --- GPU/Common/TextureReplacer.cpp | 45 ++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/GPU/Common/TextureReplacer.cpp b/GPU/Common/TextureReplacer.cpp index aa2ecc022ad0..7bd847e36ecf 100644 --- a/GPU/Common/TextureReplacer.cpp +++ b/GPU/Common/TextureReplacer.cpp @@ -95,13 +95,6 @@ void TextureReplacer::NotifyConfigChanged() { } } - if (saveEnabled_) { - // Somewhat crude message, re-using translation strings. - auto d = GetI18NCategory(I18NCat::DEVELOPER); - auto di = GetI18NCategory(I18NCat::DIALOG); - g_OSD.Show(OSDType::MESSAGE_INFO, std::string(d->T("Save new textures")) + ": " + std::string(di->T("Enabled")), 2.0f); - } - if (!replaceEnabled_ && wasReplaceEnabled) { delete vfs_; vfs_ = nullptr; @@ -115,6 +108,23 @@ void TextureReplacer::NotifyConfigChanged() { ERROR_LOG(Log::G3D, "ERROR: %s", error.c_str()); g_OSD.Show(OSDType::MESSAGE_ERROR, error, 5.0f); } + } else if (saveEnabled_) { + // Even if just saving is enabled, it makes sense to load the ini to get the correct + // settings for saving. See issue #19086 + std::string error; + bool result = LoadIni(&error); + if (!result) { + // Ignore errors here, just log if we successfully loaded an ini. + } else { + INFO_LOG(Log::G3D, "Loaded INI file for saving."); + } + } + + if (saveEnabled_) { + // Somewhat crude message, re-using translation strings. + auto d = GetI18NCategory(I18NCat::DEVELOPER); + auto di = GetI18NCategory(I18NCat::DIALOG); + g_OSD.Show(OSDType::MESSAGE_INFO, std::string(d->T("Save new textures")) + ": " + std::string(di->T("Enabled")), 2.0f); } } @@ -144,6 +154,9 @@ bool TextureReplacer::LoadIni(std::string *error) { vfsIsZip_ = false; dir = new DirectoryReader(basePath_); } else { + if (!replaceEnabled_ && saveEnabled_) { + WARN_LOG(Log::TexReplacement, "Found zip file even though only saving is enabled! This is weird."); + } vfsIsZip_ = true; } @@ -187,7 +200,9 @@ bool TextureReplacer::LoadIni(std::string *error) { delete dir; return false; } else { - WARN_LOG(Log::TexReplacement, "Texture pack lacking ini file: %s Proceeding with only hash-named textures in the root.", basePath_.c_str()); + if (replaceEnabled_) { + WARN_LOG(Log::TexReplacement, "Texture pack lacking ini file: %s Proceeding with only hash-named textures in the root.", basePath_.c_str()); + } // Do what we can do anyway: Scan for textures and build the map. std::map> filenameMap; ScanForHashNamedFiles(dir, filenameMap); @@ -202,7 +217,9 @@ bool TextureReplacer::LoadIni(std::string *error) { } auto gr = GetI18NCategory(I18NCat::GRAPHICS); - g_OSD.Show(OSDType::MESSAGE_SUCCESS, gr->T("Texture replacement pack activated"), 3.0f); + if (replaceEnabled_) { + g_OSD.Show(OSDType::MESSAGE_SUCCESS, gr->T("Texture replacement pack activated"), 3.0f); + } vfs_ = dir; @@ -212,10 +229,12 @@ bool TextureReplacer::LoadIni(std::string *error) { repl.second->vfs_ = vfs_; } - if (vfsIsZip_) { - INFO_LOG(Log::TexReplacement, "Texture pack activated from '%s'", (basePath_ / ZIP_FILENAME).c_str()); - } else { - INFO_LOG(Log::TexReplacement, "Texture pack activated from '%s'", basePath_.c_str()); + if (replaceEnabled_) { + if (vfsIsZip_) { + INFO_LOG(Log::TexReplacement, "Texture pack activated from '%s'", (basePath_ / ZIP_FILENAME).c_str()); + } else { + INFO_LOG(Log::TexReplacement, "Texture pack activated from '%s'", basePath_.c_str()); + } } // The ini doesn't have to exist for the texture directory or zip to be valid. From 74b750e30d257e119f6f2d74768573183c25db28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 13 Dec 2024 18:12:09 +0100 Subject: [PATCH 35/58] Comments --- GPU/GPUCommon.cpp | 2 ++ UI/ImDebugger/ImGe.cpp | 3 +++ 2 files changed, 5 insertions(+) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index c9ff5defe397..86a172a22dfc 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -622,6 +622,8 @@ bool GPUCommon::InterpretList(DisplayList &list) { start = time_now_d(); } + // TODO: Need to be careful when *resuming* a list (when it wasn't from a stall...) + if (list.state == PSP_GE_DL_STATE_PAUSED) return false; currentList = &list; diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index bfa8769d7b02..d69dbddd9a3c 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -457,6 +457,9 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa DisplayList list; if (gpuDebug->GetCurrentDisplayList(list)) { op = Memory::Read_U32(list.pc); + + // TODO: Also add support for block transfer previews! + bool isOnPrim = (op >> 24) == GE_CMD_PRIM; if (isOnPrim) { if (reloadPreview_) { From 024cb737161da7f4dc2e1776189cc870fdf0ef6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 13 Dec 2024 18:25:24 +0100 Subject: [PATCH 36/58] Simplify collecting time into debug counters --- Common/TimeUtil.h | 16 ++++++++++++++++ GPU/GPUCommon.cpp | 17 +++-------------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/Common/TimeUtil.h b/Common/TimeUtil.h index 0e2425da16bd..2d33b5b9543d 100644 --- a/Common/TimeUtil.h +++ b/Common/TimeUtil.h @@ -45,3 +45,19 @@ class Instant { int64_t nsecs_; #endif }; + +class TimeCollector { +public: + TimeCollector(double *target, bool enable) : target_(enable ? target : nullptr) { + if (enable) + startTime_ = time_now_d(); + } + ~TimeCollector() { + if (target_) { + *target_ += time_now_d() - startTime_; + } + } +private: + double startTime_; + double *target_; +}; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 86a172a22dfc..5d5a085eaf01 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -616,14 +616,7 @@ u32 GPUCommon::Break(int mode) { } bool GPUCommon::InterpretList(DisplayList &list) { - // Initialized to avoid a race condition with bShowDebugStats changing. - double start = 0.0; - if (coreCollectDebugStats) { - start = time_now_d(); - } - // TODO: Need to be careful when *resuming* a list (when it wasn't from a stall...) - if (list.state == PSP_GE_DL_STATE_PAUSED) return false; currentList = &list; @@ -668,9 +661,6 @@ bool GPUCommon::InterpretList(DisplayList &list) { FinishDeferred(); _dbg_assert_(!GPURecord::IsActive()); gpuState = GPUSTATE_BREAK; - if (coreCollectDebugStats) { - gpuStats.msProcessingDisplayLists += time_now_d() - start; - } return false; } } @@ -693,9 +683,6 @@ bool GPUCommon::InterpretList(DisplayList &list) { list.offsetAddr = gstate_c.offsetAddr; - if (coreCollectDebugStats) { - gpuStats.msProcessingDisplayLists += time_now_d() - start; - } return gpuState == GPUSTATE_DONE || gpuState == GPUSTATE_ERROR; } @@ -830,12 +817,14 @@ DLResult GPUCommon::ProcessDLQueue() { startingTicks = CoreTiming::GetTicks(); cyclesExecuted = 0; - // Seems to be correct behaviour to process the list anyway? + // ?? Seems to be correct behaviour to process the list anyway? if (startingTicks < busyTicks) { DEBUG_LOG(Log::G3D, "Can't execute a list yet, still busy for %lld ticks", busyTicks - startingTicks); //return; } + TimeCollector collectStat(&gpuStats.msProcessingDisplayLists, coreCollectDebugStats); + for (int listIndex = GetNextListIndex(); listIndex != -1; listIndex = GetNextListIndex()) { DisplayList &l = dls[listIndex]; DEBUG_LOG(Log::G3D, "%s DL execution at %08x - stall = %08x (startingTicks=%d)", l.pc == l.startpc ? "Starting" : "Resuming", l.pc, l.stall, startingTicks); From 58eaa3bad80492342ea34ee5d3d820fa28093f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 13 Dec 2024 18:45:18 +0100 Subject: [PATCH 37/58] Move out checking for bad PC from InterpretList --- GPU/GPUCommon.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 5d5a085eaf01..b40a55f900b5 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -628,11 +628,6 @@ bool GPUCommon::InterpretList(DisplayList &list) { gstate_c.offsetAddr = list.offsetAddr; - if (!Memory::IsValidAddress(list.pc)) { - ERROR_LOG_REPORT(Log::G3D, "DL PC = %08x WTF!!!!", list.pc); - return true; - } - cycleLastPC = list.pc; cyclesExecuted += 60; downcount = list.stall == 0 ? 0x0FFFFFFF : (list.stall - list.pc) / 4; @@ -826,9 +821,17 @@ DLResult GPUCommon::ProcessDLQueue() { TimeCollector collectStat(&gpuStats.msProcessingDisplayLists, coreCollectDebugStats); for (int listIndex = GetNextListIndex(); listIndex != -1; listIndex = GetNextListIndex()) { - DisplayList &l = dls[listIndex]; - DEBUG_LOG(Log::G3D, "%s DL execution at %08x - stall = %08x (startingTicks=%d)", l.pc == l.startpc ? "Starting" : "Resuming", l.pc, l.stall, startingTicks); - if (!InterpretList(l)) { + DisplayList &list = dls[listIndex]; + + if (!Memory::IsValidAddress(list.pc)) { + ERROR_LOG(Log::G3D, "DL PC = %08x WTF!!!!", list.pc); + return DLResult::Error; + } + + DEBUG_LOG(Log::G3D, "%s DL execution at %08x - stall = %08x (startingTicks=%d)", + list.pc == list.startpc ? "Starting" : "Resuming", list.pc, list.stall, startingTicks); + + if (!InterpretList(list)) { switch (gpuState) { case GPURunState::GPUSTATE_STALL: return DLResult::Stall; @@ -841,7 +844,7 @@ DLResult GPUCommon::ProcessDLQueue() { // Some other list could've taken the spot while we dilly-dallied around, so we need the check. // Yes, this does happen. - if (l.state != PSP_GE_DL_STATE_QUEUED) { + if (list.state != PSP_GE_DL_STATE_QUEUED) { // At the end, we can remove it from the queue and continue. dlQueue.erase(std::remove(dlQueue.begin(), dlQueue.end(), listIndex), dlQueue.end()); } From 4a8a87764cd600425007988c1c114e53703335d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 13 Dec 2024 19:15:33 +0100 Subject: [PATCH 38/58] Remove unused state --- Core/Core.cpp | 6 ++---- GPU/GPUCommon.cpp | 2 +- GPU/GPUDefinitions.h | 12 ++++++++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Core/Core.cpp b/Core/Core.cpp index 94bbdf5c7441..25705d005c45 100644 --- a/Core/Core.cpp +++ b/Core/Core.cpp @@ -190,20 +190,18 @@ void Core_RunLoopUntil(u64 globalticks) { GPUStepping::EnterStepping(); break; case DLResult::Error: - // We should elegantly report the error, or I guess ignore it. + // We should elegantly report the error somehow, or I guess ignore it. hleFinishSyscallAfterGe(); coreState = preGeCoreState; break; - case DLResult::Stall: case DLResult::Done: // Done executing for now hleFinishSyscallAfterGe(); coreState = preGeCoreState; break; default: + // Not a valid return value. _dbg_assert_(false); - hleFinishSyscallAfterGe(); - coreState = preGeCoreState; break; } break; diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index b40a55f900b5..c375ca693df3 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -834,7 +834,7 @@ DLResult GPUCommon::ProcessDLQueue() { if (!InterpretList(list)) { switch (gpuState) { case GPURunState::GPUSTATE_STALL: - return DLResult::Stall; + return DLResult::Done; case GPURunState::GPUSTATE_BREAK: return DLResult::Break; default: diff --git a/GPU/GPUDefinitions.h b/GPU/GPUDefinitions.h index f7d94cfe1b54..665b4281011d 100644 --- a/GPU/GPUDefinitions.h +++ b/GPU/GPUDefinitions.h @@ -22,7 +22,11 @@ #undef None #endif -enum DisplayListStatus { +// NOTE: This seems a bit insane, but these are two very similar enums! + +// These are return values from DrawSync(), and also sceGeDrawSync(). +// So these are frozen and "official". +enum DisplayListDrawSyncStatus { // The list has been completed PSP_GE_LIST_COMPLETED = 0, // The list is queued but not executed yet @@ -35,12 +39,13 @@ enum DisplayListStatus { PSP_GE_LIST_PAUSED = 4, }; +// These are states, stored on the lists! not sure if these are exposed, or if their values can be changed? enum DisplayListState { // No state assigned, the list is empty PSP_GE_DL_STATE_NONE = 0, // The list has been queued PSP_GE_DL_STATE_QUEUED = 1, - // The list is being executed + // The list is being executed (or stalled?) PSP_GE_DL_STATE_RUNNING = 2, // The list was completed and will be removed PSP_GE_DL_STATE_COMPLETED = 3, @@ -60,8 +65,7 @@ enum GPUInvalidationType { }; enum class DLResult { - Done, + Done, // Or stall Error, - Stall, Break, // used for stepping, breakpoints }; From 3465993af91db632ec0574f5015ee24247742c1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 13 Dec 2024 19:24:11 +0100 Subject: [PATCH 39/58] Minor code cleanups --- GPU/GPUCommon.cpp | 96 ++++++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 43 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index c375ca693df3..3908e305ecd3 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -617,8 +617,6 @@ u32 GPUCommon::Break(int mode) { bool GPUCommon::InterpretList(DisplayList &list) { // TODO: Need to be careful when *resuming* a list (when it wasn't from a stall...) - if (list.state == PSP_GE_DL_STATE_PAUSED) - return false; currentList = &list; if (!list.started && list.context.IsValid()) { @@ -831,15 +829,11 @@ DLResult GPUCommon::ProcessDLQueue() { DEBUG_LOG(Log::G3D, "%s DL execution at %08x - stall = %08x (startingTicks=%d)", list.pc == list.startpc ? "Starting" : "Resuming", list.pc, list.stall, startingTicks); + if (list.state == PSP_GE_DL_STATE_PAUSED) + goto bail; + if (!InterpretList(list)) { - switch (gpuState) { - case GPURunState::GPUSTATE_STALL: - return DLResult::Done; - case GPURunState::GPUSTATE_BREAK: - return DLResult::Break; - default: - return DLResult::Error; - } + goto bail; } // Some other list could've taken the spot while we dilly-dallied around, so we need the check. @@ -862,6 +856,16 @@ DLResult GPUCommon::ProcessDLQueue() { __GeTriggerSync(GPU_SYNC_DRAW, 1, drawCompleteTicks); // Since the event is in CoreTiming, we're in sync. Just set 0 now. return DLResult::Done; + +bail: + switch (gpuState) { + case GPURunState::GPUSTATE_STALL: + return DLResult::Done; + case GPURunState::GPUSTATE_BREAK: + return DLResult::Break; + default: + return DLResult::Error; + } } bool GPUCommon::ShouldSplitOverGe() const { @@ -929,6 +933,9 @@ void GPUCommon::Execute_Call(u32 op, u32 diff) { } void GPUCommon::DoExecuteCall(u32 target) { + // Local variable for better codegen + DisplayList *currentList = this->currentList; + // Bone matrix optimization - many games will CALL a bone matrix (!). // We don't optimize during recording - so the matrix data gets recorded. if (!debugRecording_ && Memory::IsValidRange(target, 13 * 4) && (Memory::ReadUnchecked_U32(target) >> 24) == GE_CMD_BONEMATRIXDATA) { @@ -958,6 +965,8 @@ void GPUCommon::DoExecuteCall(u32 target) { } void GPUCommon::Execute_Ret(u32 op, u32 diff) { + // Local variable for better codegen + DisplayList *currentList = this->currentList; if (currentList->stackptr == 0) { DEBUG_LOG(Log::G3D, "RET: Stack empty!"); } else { @@ -1159,7 +1168,7 @@ void GPUCommon::Execute_End(u32 op, u32 diff) { } break; default: - DEBUG_LOG(Log::G3D,"Ah, not finished: %06x", prev & 0xFFFFFF); + DEBUG_LOG(Log::G3D, "END: Not finished: %06x", prev & 0xFFFFFF); break; } } @@ -1179,43 +1188,44 @@ void GPUCommon::Execute_BoundingBox(u32 op, u32 diff) { VertexDecoder *dec = drawEngineCommon_->GetVertexDecoder(gstate.vertType); int bytesRead = (useInds ? 1 : dec->VertexSize()) * count; - if (Memory::IsValidRange(gstate_c.vertexAddr, bytesRead)) { - const void *control_points = Memory::GetPointerUnchecked(gstate_c.vertexAddr); - if (!control_points) { - ERROR_LOG_REPORT_ONCE(boundingbox, Log::G3D, "Invalid verts in bounding box check"); + if (!Memory::IsValidRange(gstate_c.vertexAddr, bytesRead)) { + ERROR_LOG_REPORT_ONCE(boundingbox, Log::G3D, "Bad bounding box data: %06x", count); + // Data seems invalid. Let's assume the box test passed. + currentList->bboxResult = true; + return; + } + + const void *control_points = Memory::GetPointerUnchecked(gstate_c.vertexAddr); + if (!control_points) { + ERROR_LOG_REPORT_ONCE(boundingbox, Log::G3D, "Invalid verts in bounding box check"); + currentList->bboxResult = true; + return; + } + + const void *inds = nullptr; + if (useInds) { + int indexShift = ((gstate.vertType & GE_VTYPE_IDX_MASK) >> GE_VTYPE_IDX_SHIFT) - 1; + inds = Memory::GetPointerUnchecked(gstate_c.indexAddr); + if (!inds || !Memory::IsValidRange(gstate_c.indexAddr, count << indexShift)) { + ERROR_LOG_REPORT_ONCE(boundingboxInds, Log::G3D, "Invalid inds in bounding box check"); currentList->bboxResult = true; return; } + } - const void *inds = nullptr; - if (useInds) { - int indexShift = ((gstate.vertType & GE_VTYPE_IDX_MASK) >> GE_VTYPE_IDX_SHIFT) - 1; - inds = Memory::GetPointerUnchecked(gstate_c.indexAddr); - if (!inds || !Memory::IsValidRange(gstate_c.indexAddr, count << indexShift)) { - ERROR_LOG_REPORT_ONCE(boundingboxInds, Log::G3D, "Invalid inds in bounding box check"); - currentList->bboxResult = true; - return; - } - } - - // Test if the bounding box is within the drawing region. - // The PSP only seems to vary the result based on a single range of 0x100. - if (count > 0x200) { - // The second to last set of 0x100 is checked (even for odd counts.) - size_t skipSize = (count - 0x200) * dec->VertexSize(); - currentList->bboxResult = drawEngineCommon_->TestBoundingBox((const uint8_t *)control_points + skipSize, inds, 0x100, gstate.vertType); - } else if (count > 0x100) { - int checkSize = count - 0x100; - currentList->bboxResult = drawEngineCommon_->TestBoundingBox(control_points, inds, checkSize, gstate.vertType); - } else { - currentList->bboxResult = drawEngineCommon_->TestBoundingBox(control_points, inds, count, gstate.vertType); - } - AdvanceVerts(gstate.vertType, count, bytesRead); + // Test if the bounding box is within the drawing region. + // The PSP only seems to vary the result based on a single range of 0x100. + if (count > 0x200) { + // The second to last set of 0x100 is checked (even for odd counts.) + size_t skipSize = (count - 0x200) * dec->VertexSize(); + currentList->bboxResult = drawEngineCommon_->TestBoundingBox((const uint8_t *)control_points + skipSize, inds, 0x100, gstate.vertType); + } else if (count > 0x100) { + int checkSize = count - 0x100; + currentList->bboxResult = drawEngineCommon_->TestBoundingBox(control_points, inds, checkSize, gstate.vertType); } else { - ERROR_LOG_REPORT_ONCE(boundingbox, Log::G3D, "Bad bounding box data: %06x", count); - // Data seems invalid. Let's assume the box test passed. - currentList->bboxResult = true; + currentList->bboxResult = drawEngineCommon_->TestBoundingBox(control_points, inds, count, gstate.vertType); } + AdvanceVerts(gstate.vertType, count, bytesRead); } void GPUCommon::Execute_MorphWeight(u32 op, u32 diff) { @@ -1234,7 +1244,7 @@ void GPUCommon::Execute_ImmVertexAlphaPrim(u32 op, u32 diff) { return; } - int prim = (op >> 8) & 0x7; + const int prim = (op >> 8) & 0x7; if (prim != GE_PRIM_KEEP_PREVIOUS) { // Flush before changing the prim type. Only continue can be used to continue a prim. FlushImm(); From 7643d38700e2a1fb13c4f056056c534ee785e38b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 13 Dec 2024 19:29:06 +0100 Subject: [PATCH 40/58] Inline InterpretList into ProcessDLQueue. Makes it easier to understand the flow. --- GPU/GPUCommon.cpp | 133 +++++++++++++++++++++++----------------------- GPU/GPUCommon.h | 2 - 2 files changed, 66 insertions(+), 69 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 3908e305ecd3..b3bd9bf0340b 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -615,70 +615,6 @@ u32 GPUCommon::Break(int mode) { return currentList->id; } -bool GPUCommon::InterpretList(DisplayList &list) { - // TODO: Need to be careful when *resuming* a list (when it wasn't from a stall...) - currentList = &list; - - if (!list.started && list.context.IsValid()) { - gstate.Save(list.context); - } - list.started = true; - - gstate_c.offsetAddr = list.offsetAddr; - - cycleLastPC = list.pc; - cyclesExecuted += 60; - downcount = list.stall == 0 ? 0x0FFFFFFF : (list.stall - list.pc) / 4; - list.state = PSP_GE_DL_STATE_RUNNING; - list.interrupted = false; - - gpuState = list.pc == list.stall ? GPUSTATE_STALL : GPUSTATE_RUNNING; - - // To enable breakpoints, we don't do fast matrix loads while debugger active. - debugRecording_ = GPUDebug::IsActive() || GPURecord::IsActive(); - const bool useFastRunLoop = !dumpThisFrame_ && !debugRecording_; - while (gpuState == GPUSTATE_RUNNING) { - if (list.pc == list.stall) { - gpuState = GPUSTATE_STALL; - downcount = 0; - } - - if (useFastRunLoop) { - // When no Ge debugger is active, we go full speed. - FastRunLoop(list); - } else { - // When a Ge debugger is active (or similar), we do more checking. - if (!SlowRunLoop(list)) { - // Hit a breakpoint, so we set the state and bail. We can resume later. - // TODO: Cycle counting might need some more care? - FinishDeferred(); - _dbg_assert_(!GPURecord::IsActive()); - gpuState = GPUSTATE_BREAK; - return false; - } - } - - downcount = list.stall == 0 ? 0x0FFFFFFF : (list.stall - list.pc) / 4; - if (gpuState == GPUSTATE_STALL && list.pc != list.stall) { - // Unstalled (Can this happen?) - gpuState = GPUSTATE_RUNNING; - } - } - - FinishDeferred(); - if (debugRecording_) - GPURecord::NotifyCPU(); - - // We haven't run the op at list.pc, so it shouldn't count. - if (cycleLastPC != list.pc) { - UpdatePC(list.pc - 4, list.pc); - } - - list.offsetAddr = gstate_c.offsetAddr; - - return gpuState == GPUSTATE_DONE || gpuState == GPUSTATE_ERROR; -} - void GPUCommon::PSPFrame() { immCount_ = 0; if (dumpNextFrame_) { @@ -829,10 +765,73 @@ DLResult GPUCommon::ProcessDLQueue() { DEBUG_LOG(Log::G3D, "%s DL execution at %08x - stall = %08x (startingTicks=%d)", list.pc == list.startpc ? "Starting" : "Resuming", list.pc, list.stall, startingTicks); - if (list.state == PSP_GE_DL_STATE_PAUSED) - goto bail; + if (list.state == PSP_GE_DL_STATE_PAUSED) { + return DLResult::Done; + } + + // TODO: Need to be careful when *resuming* a list (when it wasn't from a stall...) + currentList = &list; + + if (!list.started && list.context.IsValid()) { + gstate.Save(list.context); + } + list.started = true; + + gstate_c.offsetAddr = list.offsetAddr; - if (!InterpretList(list)) { + cycleLastPC = list.pc; + cyclesExecuted += 60; + downcount = list.stall == 0 ? 0x0FFFFFFF : (list.stall - list.pc) / 4; + list.state = PSP_GE_DL_STATE_RUNNING; + list.interrupted = false; + + gpuState = list.pc == list.stall ? GPUSTATE_STALL : GPUSTATE_RUNNING; + + // To enable breakpoints, we don't do fast matrix loads while debugger active. + debugRecording_ = GPUDebug::IsActive() || GPURecord::IsActive(); + const bool useFastRunLoop = !dumpThisFrame_ && !debugRecording_; + while (gpuState == GPUSTATE_RUNNING) { + if (list.pc == list.stall) { + gpuState = GPUSTATE_STALL; + downcount = 0; + } + + if (useFastRunLoop) { + // When no Ge debugger is active, we go full speed. + FastRunLoop(list); + } else { + // When a Ge debugger is active (or similar), we do more checking. + if (!SlowRunLoop(list)) { + // Hit a breakpoint, so we set the state and bail. We can resume later. + // TODO: Cycle counting might need some more care? + FinishDeferred(); + _dbg_assert_(!GPURecord::IsActive()); + gpuState = GPUSTATE_BREAK; + return DLResult::Break; + } + } + + downcount = list.stall == 0 ? 0x0FFFFFFF : (list.stall - list.pc) / 4; + if (gpuState == GPUSTATE_STALL && list.pc != list.stall) { + // Unstalled (Can this happen?) + gpuState = GPUSTATE_RUNNING; + } + } + + FinishDeferred(); + if (debugRecording_) + GPURecord::NotifyCPU(); + + // We haven't run the op at list.pc, so it shouldn't count. + if (cycleLastPC != list.pc) { + UpdatePC(list.pc - 4, list.pc); + } + + list.offsetAddr = gstate_c.offsetAddr; + + if (gpuState == GPUSTATE_DONE || gpuState == GPUSTATE_ERROR) { + // return true + } else { goto bail; } diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index ec7bb50deb0e..c3b77e1a741f 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -236,8 +236,6 @@ class GPUCommon : public GPUDebugInterface { virtual void PreExecuteOp(u32 op, u32 diff) {} - bool InterpretList(DisplayList &list); - DLResult ProcessDLQueue(); u32 UpdateStall(int listid, u32 newstall, bool *runList); From 9a3cc7546b11e68cd4919363cc6d2af1a374d2a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 13 Dec 2024 19:35:40 +0100 Subject: [PATCH 41/58] One more bit of simplification --- GPU/GPUCommon.cpp | 24 +++++++++--------------- GPU/GPUCommon.h | 1 - 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index b3bd9bf0340b..66789af40131 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -806,7 +806,6 @@ DLResult GPUCommon::ProcessDLQueue() { // TODO: Cycle counting might need some more care? FinishDeferred(); _dbg_assert_(!GPURecord::IsActive()); - gpuState = GPUSTATE_BREAK; return DLResult::Break; } } @@ -829,10 +828,15 @@ DLResult GPUCommon::ProcessDLQueue() { list.offsetAddr = gstate_c.offsetAddr; - if (gpuState == GPUSTATE_DONE || gpuState == GPUSTATE_ERROR) { - // return true - } else { - goto bail; + switch (gpuState) { + case GPUSTATE_DONE: + case GPUSTATE_ERROR: + // don't do anything - though dunno about error... + break; + case GPUSTATE_STALL: + return DLResult::Done; + default: + return DLResult::Error; } // Some other list could've taken the spot while we dilly-dallied around, so we need the check. @@ -855,16 +859,6 @@ DLResult GPUCommon::ProcessDLQueue() { __GeTriggerSync(GPU_SYNC_DRAW, 1, drawCompleteTicks); // Since the event is in CoreTiming, we're in sync. Just set 0 now. return DLResult::Done; - -bail: - switch (gpuState) { - case GPURunState::GPUSTATE_STALL: - return DLResult::Done; - case GPURunState::GPUSTATE_BREAK: - return DLResult::Break; - default: - return DLResult::Error; - } } bool GPUCommon::ShouldSplitOverGe() const { diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index c3b77e1a741f..d45e7ca66110 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -80,7 +80,6 @@ enum GPURunState { GPUSTATE_STALL = 2, GPUSTATE_INTERRUPT = 3, GPUSTATE_ERROR = 4, - GPUSTATE_BREAK = 5, }; enum GPUSyncType { From 6baaa2607a2771202eece759304ab524eec2e344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 13 Dec 2024 20:36:31 +0100 Subject: [PATCH 42/58] GE debugger: Cleaner resume from steps. Fixes GE debugging in God of War. --- Core/Core.cpp | 2 +- GPU/GPUCommon.cpp | 64 ++++++++++++++++++++++++++++---------------- GPU/GPUCommon.h | 1 + GPU/GPUDefinitions.h | 2 +- 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/Core/Core.cpp b/Core/Core.cpp index 25705d005c45..d843047808dc 100644 --- a/Core/Core.cpp +++ b/Core/Core.cpp @@ -186,7 +186,7 @@ void Core_RunLoopUntil(u64 globalticks) { break; // Will loop around to go to RUNNING_GE or NEXTFRAME, which will exit. case CORE_RUNNING_GE: switch (gpu->ProcessDLQueue()) { - case DLResult::Break: + case DLResult::DebugBreak: GPUStepping::EnterStepping(); break; case DLResult::Error: diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 66789af40131..ec29fbad3214 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -743,13 +743,15 @@ int GPUCommon::GetNextListIndex() { // This is now called when coreState == CORE_RUNNING_GE. // TODO: It should return the next action.. (break into debugger or continue running) DLResult GPUCommon::ProcessDLQueue() { - startingTicks = CoreTiming::GetTicks(); - cyclesExecuted = 0; - - // ?? Seems to be correct behaviour to process the list anyway? - if (startingTicks < busyTicks) { - DEBUG_LOG(Log::G3D, "Can't execute a list yet, still busy for %lld ticks", busyTicks - startingTicks); - //return; + if (!resumingFromDebugBreak_) { + startingTicks = CoreTiming::GetTicks(); + cyclesExecuted = 0; + + // ?? Seems to be correct behaviour to process the list anyway? + if (startingTicks < busyTicks) { + DEBUG_LOG(Log::G3D, "Can't execute a list yet, still busy for %lld ticks", busyTicks - startingTicks); + //return; + } } TimeCollector collectStat(&gpuStats.msProcessingDisplayLists, coreCollectDebugStats); @@ -769,27 +771,41 @@ DLResult GPUCommon::ProcessDLQueue() { return DLResult::Done; } - // TODO: Need to be careful when *resuming* a list (when it wasn't from a stall...) - currentList = &list; + if (!resumingFromDebugBreak_) { + // TODO: Need to be careful when *resuming* a list (when it wasn't from a stall...) + currentList = &list; - if (!list.started && list.context.IsValid()) { - gstate.Save(list.context); - } - list.started = true; + if (!list.started && list.context.IsValid()) { + gstate.Save(list.context); + } + list.started = true; - gstate_c.offsetAddr = list.offsetAddr; + gstate_c.offsetAddr = list.offsetAddr; - cycleLastPC = list.pc; - cyclesExecuted += 60; - downcount = list.stall == 0 ? 0x0FFFFFFF : (list.stall - list.pc) / 4; - list.state = PSP_GE_DL_STATE_RUNNING; - list.interrupted = false; + cycleLastPC = list.pc; + cyclesExecuted += 60; + downcount = list.stall == 0 ? 0x0FFFFFFF : (list.stall - list.pc) / 4; + list.state = PSP_GE_DL_STATE_RUNNING; + list.interrupted = false; - gpuState = list.pc == list.stall ? GPUSTATE_STALL : GPUSTATE_RUNNING; + gpuState = list.pc == list.stall ? GPUSTATE_STALL : GPUSTATE_RUNNING; + + // To enable breakpoints, we don't do fast matrix loads while debugger active. + debugRecording_ = GPUDebug::IsActive() || GPURecord::IsActive(); + } else { + resumingFromDebugBreak_ = false; + // The bottom part of the gpuState loop below, that wasn't executed + // when we bailed. + downcount = list.stall == 0 ? 0x0FFFFFFF : (list.stall - list.pc) / 4; + if (gpuState == GPUSTATE_STALL && list.pc != list.stall) { + // Unstalled (Can this happen?) + gpuState = GPUSTATE_RUNNING; + } + // Proceed... + } - // To enable breakpoints, we don't do fast matrix loads while debugger active. - debugRecording_ = GPUDebug::IsActive() || GPURecord::IsActive(); const bool useFastRunLoop = !dumpThisFrame_ && !debugRecording_; + while (gpuState == GPUSTATE_RUNNING) { if (list.pc == list.stall) { gpuState = GPUSTATE_STALL; @@ -806,7 +822,9 @@ DLResult GPUCommon::ProcessDLQueue() { // TODO: Cycle counting might need some more care? FinishDeferred(); _dbg_assert_(!GPURecord::IsActive()); - return DLResult::Break; + + resumingFromDebugBreak_ = true; + return DLResult::DebugBreak; } } diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index d45e7ca66110..cf442ed61c37 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -456,6 +456,7 @@ class GPUCommon : public GPUDebugInterface { u32 cycleLastPC; int cyclesExecuted; + bool resumingFromDebugBreak_ = false; bool dumpNextFrame_ = false; bool dumpThisFrame_ = false; bool debugRecording_; diff --git a/GPU/GPUDefinitions.h b/GPU/GPUDefinitions.h index 665b4281011d..a3d93661ed78 100644 --- a/GPU/GPUDefinitions.h +++ b/GPU/GPUDefinitions.h @@ -67,5 +67,5 @@ enum GPUInvalidationType { enum class DLResult { Done, // Or stall Error, - Break, // used for stepping, breakpoints + DebugBreak, // used for stepping, breakpoints }; From 3cc7d6ef7a472b09f7f3639b8ca858d99601f268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 13 Dec 2024 21:58:22 +0100 Subject: [PATCH 43/58] ImDebugger: Assorted UI improvements --- UI/ImDebugger/ImDebugger.cpp | 46 +++++++++++++++++++++------------- UI/ImDebugger/ImDebugger.h | 3 +++ UI/ImDebugger/ImDisasmView.cpp | 6 +---- UI/ImDebugger/ImGe.cpp | 34 +++++++++++++------------ UI/ImDebugger/ImMemView.cpp | 23 ++++++----------- 5 files changed, 58 insertions(+), 54 deletions(-) diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 4acde09d8520..c7d0a09c8827 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -64,6 +64,14 @@ void ShowInWindowMenuItems(uint32_t addr, ImControl &control) { } } +void StatusBar(std::string_view status) { + ImGui::TextUnformatted(status.data(), status.data() + status.length()); + ImGui::SameLine(); + if (ImGui::SmallButton("Copy")) { + System_CopyStringToClipboard(status); + } +} + // TODO: Style it. // Left click performs the preferred action, if any. Right click opens a menu for more. void ImClickableAddress(uint32_t addr, ImControl &control, ImCmd cmd) { @@ -1170,8 +1178,10 @@ void ImMemWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl & memView_.gotoAddr(gotoAddr_); } + ImVec2 size(0, -ImGui::GetFrameHeightWithSpacing()); + // Main views - list of interesting addresses to the left, memory view to the right. - if (ImGui::BeginChild("addr_list", ImVec2(200.0f, 0.0))) { + if (ImGui::BeginChild("addr_list", ImVec2(200.0f, size.y), ImGuiChildFlags_ResizeX)) { if (ImGui::Selectable("Scratch")) { GotoAddr(0x00010000); } @@ -1182,18 +1192,19 @@ void ImMemWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl & GotoAddr(0x08800000); } if (ImGui::Selectable("VRAM")) { - GotoAddr(0x08800000); + GotoAddr(0x04000000); } } ImGui::EndChild(); ImGui::SameLine(); - if (ImGui::BeginChild("memview", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()))) { + if (ImGui::BeginChild("memview", size)) { memView_.Draw(ImGui::GetWindowDrawList()); } ImGui::EndChild(); - ImGui::TextUnformatted(memView_.StatusMessage().c_str()); + StatusBar(memView_.StatusMessage()); + ImGui::End(); } @@ -1206,7 +1217,7 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImContro disasmView_.setDebugger(mipsDebug); ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); - if (!ImGui::Begin(Title(), &cfg.disasmOpen, ImGuiWindowFlags_NoNavInputs)) { + if (!ImGui::Begin(Title(), &cfg.disasmOpen)) { ImGui::End(); return; } @@ -1273,10 +1284,11 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImContro Core_RequestCPUStep(CPUStepType::Out, 0); } + /* ImGui::SameLine(); if (ImGui::SmallButton("Frame")) { Core_RequestCPUStep(CPUStepType::Frame, 0); - } + }*/ ImGui::SameLine(); if (ImGui::SmallButton("Syscall")) { @@ -1346,12 +1358,10 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImContro disasmView_.scrollAddressIntoView(); } - if (ImGui::BeginTable("main", 2)) { - ImGui::TableSetupColumn("left", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("right", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); + ImVec2 avail = ImGui::GetContentRegionAvail(); + avail.y -= ImGui::GetTextLineHeightWithSpacing(); + if (ImGui::BeginChild("left", ImVec2(150.0f, avail.y), ImGuiChildFlags_ResizeX)) { if (symCache_.empty() || symsDirty_) { symCache_ = g_symbolMap->GetAllSymbols(SymbolType::ST_FUNCTION); symsDirty_ = false; @@ -1369,8 +1379,7 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImContro } } - ImVec2 sz = ImGui::GetContentRegionAvail(); - if (ImGui::BeginListBox("##symbols", ImVec2(150.0, sz.y - ImGui::GetTextLineHeightWithSpacing() * 2))) { + if (ImGui::BeginListBox("##symbols", ImGui::GetContentRegionAvail())) { ImGuiListClipper clipper; clipper.Begin((int)symCache_.size(), -1); while (clipper.Step()) { @@ -1386,13 +1395,16 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImContro clipper.End(); ImGui::EndListBox(); } + } + ImGui::EndChild(); - ImGui::TableSetColumnIndex(1); + ImGui::SameLine(); + if (ImGui::BeginChild("right", ImVec2(0.0f, avail.y))) { disasmView_.Draw(ImGui::GetWindowDrawList(), control); - ImGui::EndTable(); - - ImGui::TextUnformatted(disasmView_.StatusBarText().c_str()); } + ImGui::EndChild(); + + StatusBar(disasmView_.StatusBarText()); ImGui::End(); } diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h index 9ffb9ce7bbba..62bd71b49c97 100644 --- a/UI/ImDebugger/ImDebugger.h +++ b/UI/ImDebugger/ImDebugger.h @@ -76,6 +76,7 @@ class ImMemWindow { symsDirty_ = true; } void GotoAddr(u32 addr) { + gotoAddr_ = addr; memView_.gotoAddr(addr); } static const char *Title(int index); @@ -122,6 +123,7 @@ struct ImConfig { bool geDebuggerOpen; bool geStateOpen; bool schedulerOpen; + bool watchOpen; bool memViewOpen[4]; // HLE explorer settings @@ -195,3 +197,4 @@ class ImDebugger { void ImClickableAddress(uint32_t addr, ImControl &control, ImCmd cmd); void ShowInWindowMenuItems(uint32_t addr, ImControl &control); void ShowInMemoryViewerMenuItem(uint32_t addr, ImControl &control); +void StatusBar(std::string_view str); diff --git a/UI/ImDebugger/ImDisasmView.cpp b/UI/ImDebugger/ImDisasmView.cpp index 606faea5f12d..b5209e10dff5 100644 --- a/UI/ImDebugger/ImDisasmView.cpp +++ b/UI/ImDebugger/ImDisasmView.cpp @@ -309,9 +309,7 @@ void ImDisasmView::Draw(ImDrawList *drawList, ImControl &control) { ImVec2 canvas_p0 = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates! ImVec2 canvas_sz = ImGui::GetContentRegionAvail(); // Resize canvas to what's available - if (canvas_sz.x < 50.0f) canvas_sz.x = 50.0f; - if (canvas_sz.y < 50.0f) canvas_sz.y = 50.0f; - canvas_sz.y -= rowHeight_ * 2.0f; // space for status bar + const ImVec2 canvas_p1 = ImVec2(canvas_p0.x + canvas_sz.x, canvas_p0.y + canvas_sz.y); // This will catch our interactions bool pressed = ImGui::InvisibleButton("canvas", canvas_sz, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); @@ -323,8 +321,6 @@ void ImDisasmView::Draw(ImDrawList *drawList, ImControl &control) { } ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelY); - const ImVec2 canvas_p1 = ImVec2(canvas_p0.x + canvas_sz.x, canvas_p0.y + canvas_sz.y); - drawList->PushClipRect(canvas_p0, canvas_p1, true); drawList->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(25, 25, 25, 255)); if (is_active) { diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index d69dbddd9a3c..96a3362ba0df 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -426,23 +426,25 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa ImGui::BeginChild("left pane", ImVec2(400, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeX); - for (auto index : gpuDebug->GetDisplayListQueue()) { - const auto &list = gpuDebug->GetDisplayList(index); - char title[64]; - snprintf(title, sizeof(title), "List %d", list.id); - if (ImGui::CollapsingHeader(title)) { - ImGui::Text("State: %s", DLStateToString(list.state)); - ImGui::TextUnformatted("PC:"); - ImGui::SameLine(); - ImClickableAddress(list.pc, control, ImCmd::SHOW_IN_GE_DISASM); - ImGui::Text("StartPC:"); - ImGui::SameLine(); - ImClickableAddress(list.startpc, control, ImCmd::SHOW_IN_GE_DISASM); - if (list.pendingInterrupt) { - ImGui::TextUnformatted("(Pending interrupt)"); + if (ImGui::CollapsingHeader("Display lists")) { + for (auto index : gpuDebug->GetDisplayListQueue()) { + const auto &list = gpuDebug->GetDisplayList(index); + char title[64]; + snprintf(title, sizeof(title), "List %d", list.id); + if (ImGui::CollapsingHeader(title)) { + ImGui::Text("State: %s", DLStateToString(list.state)); + ImGui::TextUnformatted("PC:"); + ImGui::SameLine(); + ImClickableAddress(list.pc, control, ImCmd::SHOW_IN_GE_DISASM); + ImGui::Text("StartPC:"); + ImGui::SameLine(); + ImClickableAddress(list.startpc, control, ImCmd::SHOW_IN_GE_DISASM); + if (list.pendingInterrupt) { + ImGui::TextUnformatted("(Pending interrupt)"); + } + ImGui::Text("Stack depth: %d", (int)list.stackptr); + ImGui::Text("BBOX result: %d", (int)list.bboxResult); } - ImGui::Text("Stack depth: %d", (int)list.stackptr); - ImGui::Text("BBOX result: %d", (int)list.bboxResult); } } diff --git a/UI/ImDebugger/ImMemView.cpp b/UI/ImDebugger/ImMemView.cpp index 99f2d8e134c4..e8718767293c 100644 --- a/UI/ImDebugger/ImMemView.cpp +++ b/UI/ImDebugger/ImMemView.cpp @@ -27,17 +27,6 @@ ImMemView::ImMemView() { ImMemView::~ImMemView() {} -/* -LRESULT CALLBACK ImMemView::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { - if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) { - ccp->ScrollWindow(-3, GotoModeFromModifiers(false)); - } else if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) { - ccp->ScrollWindow(3, GotoModeFromModifiers(false)); - } - return DefWindowProc(hwnd, msg, wParam, lParam); -} -*/ - static uint32_t pickTagColor(std::string_view tag) { uint32_t colors[6] = { 0xFF301010, 0xFF103030, 0xFF403010, 0xFF103000, 0xFF301030, 0xFF101030 }; int which = XXH3_64bits(tag.data(), tag.length()) % ARRAY_SIZE(colors); @@ -107,7 +96,7 @@ void ImMemView::Draw(ImDrawList *drawList) { char temp[32]; uint32_t address = windowStart_ + i * rowSize_; - snprintf(temp, sizeof(temp), "%08X", address); + snprintf(temp, sizeof(temp), "%08x", address); drawList->AddText(ImVec2(canvas_p0.x + addressStartX_, canvas_p0.y + rowY), IM_COL32(0xE0, 0xE0, 0xE0, 0xFF), temp); union { @@ -136,7 +125,7 @@ void ImMemView::Draw(ImDrawList *drawList) { char c; if (valid) { - snprintf(temp, sizeof(temp), "%02X ", memory.bytes[j]); + snprintf(temp, sizeof(temp), "%02x ", memory.bytes[j]); c = (char)memory.bytes[j]; if (memory.bytes[j] < 32 || memory.bytes[j] >= 128) c = '.'; @@ -188,8 +177,8 @@ void ImMemView::Draw(ImDrawList *drawList) { if (bg != 0) { int bgWidth = 2; // continueRect ? 3 : 2; drawList->AddRectFilled(ImVec2(canvas_p0.x + hexX - 1, canvas_p0.y + rowY), ImVec2(canvas_p0.x + hexX + charWidth_ * bgWidth, canvas_p0.y + rowY + charHeight_), bg); - drawList->AddText(ImVec2(canvas_p0.x + hexX, canvas_p0.y + rowY), fg, &temp[0], &temp[2]); } + drawList->AddText(ImVec2(canvas_p0.x + hexX, canvas_p0.y + rowY), fg, &temp[0], &temp[2]); if (underline >= 0) { float x = canvas_p0.x + hexX + underline * charWidth_; drawList->AddRectFilled(ImVec2(x, canvas_p0.y + rowY + charHeight_ - 2), ImVec2(x + charWidth_, canvas_p0.y + rowY + charHeight_), IM_COL32(0xFF, 0xFF, 0xFF, 0xFF)); @@ -197,7 +186,9 @@ void ImMemView::Draw(ImDrawList *drawList) { fg = asciiTextCol; bg = asciiBGCol; - drawList->AddRectFilled(ImVec2(canvas_p0.x + asciiX, canvas_p0.y + rowY), ImVec2(canvas_p0.x + asciiX + charWidth_, canvas_p0.y + rowY + charHeight_), bg); + if (bg) { + drawList->AddRectFilled(ImVec2(canvas_p0.x + asciiX, canvas_p0.y + rowY), ImVec2(canvas_p0.x + asciiX + charWidth_, canvas_p0.y + rowY + charHeight_), bg); + } drawList->AddText(ImVec2(canvas_p0.x + asciiX, canvas_p0.y + rowY), fg, &c, &c + 1); } } @@ -533,7 +524,7 @@ void ImMemView::updateStatusBarText() { snprintf(text, sizeof(text), "%08x", curAddress_); // There should only be one. for (MemBlockInfo info : memRangeInfo) { - snprintf(text, sizeof(text), "%08x - %s %08x-%08x (alloc'd at PC %08x / %lld ticks)", curAddress_, info.tag.c_str(), info.start, info.start + info.size, info.pc, info.ticks); + snprintf(text, sizeof(text), "%08x - %s %08x-%08x (PC %08x / %lld ticks)", curAddress_, info.tag.c_str(), info.start, info.start + info.size, info.pc, info.ticks); } statusMessage_ = text; } From 8cc77c0997f9a587cb39c19ddf39ce1efee13075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 13 Dec 2024 22:05:32 +0100 Subject: [PATCH 44/58] ImGui: Implement cursor support on Windows --- Windows/MainWindow.cpp | 26 ++++++++++++++++++++++++++ ext/imgui/imgui_impl_platform.cpp | 7 +++++++ ext/imgui/imgui_impl_platform.h | 2 ++ 3 files changed, 35 insertions(+) diff --git a/Windows/MainWindow.cpp b/Windows/MainWindow.cpp index 9cb88a34d6dd..24dad54226df 100644 --- a/Windows/MainWindow.cpp +++ b/Windows/MainWindow.cpp @@ -41,6 +41,7 @@ #include "Common/Input/KeyCodes.h" #include "Common/Thread/ThreadUtil.h" #include "Common/Data/Encoding/Utf8.h" +#include "ext/imgui/imgui_impl_platform.h" #include "Core/Core.h" #include "Core/Config.h" @@ -611,6 +612,31 @@ namespace MainWindow case WM_SETFOCUS: break; + case WM_SETCURSOR: + if ((lParam & 0xFFFF) == HTCLIENT && g_Config.bShowImDebugger) { + LPTSTR win32_cursor = 0; + switch (ImGui_ImplPlatform_GetCursor()) { + case ImGuiMouseCursor_Arrow: win32_cursor = IDC_ARROW; break; + case ImGuiMouseCursor_TextInput: win32_cursor = IDC_IBEAM; break; + case ImGuiMouseCursor_ResizeAll: win32_cursor = IDC_SIZEALL; break; + case ImGuiMouseCursor_ResizeEW: win32_cursor = IDC_SIZEWE; break; + case ImGuiMouseCursor_ResizeNS: win32_cursor = IDC_SIZENS; break; + case ImGuiMouseCursor_ResizeNESW: win32_cursor = IDC_SIZENESW; break; + case ImGuiMouseCursor_ResizeNWSE: win32_cursor = IDC_SIZENWSE; break; + case ImGuiMouseCursor_Hand: win32_cursor = IDC_HAND; break; + case ImGuiMouseCursor_NotAllowed: win32_cursor = IDC_NO; break; + } + if (win32_cursor) { + SetCursor(::LoadCursor(nullptr, win32_cursor)); + } else { + SetCursor(nullptr); + } + return TRUE; + } else { + return DefWindowProc(hWnd, message, wParam, lParam); + } + break; + case WM_ERASEBKGND: if (firstErase) { firstErase = false; diff --git a/ext/imgui/imgui_impl_platform.cpp b/ext/imgui/imgui_impl_platform.cpp index e0afb444d281..aaa606d47013 100644 --- a/ext/imgui/imgui_impl_platform.cpp +++ b/ext/imgui/imgui_impl_platform.cpp @@ -9,6 +9,8 @@ #include "imgui_impl_platform.h" +static ImGuiMouseCursor g_cursor = ImGuiMouseCursor_Arrow; + void ImGui_ImplPlatform_KeyEvent(const KeyInput &key) { ImGuiIO &io = ImGui::GetIO(); @@ -94,6 +96,7 @@ void ImGui_ImplPlatform_NewFrame() { double now = time_now_d(); + g_cursor = ImGui::GetMouseCursor(); ImGuiIO &io = ImGui::GetIO(); io.DisplaySize = ImVec2(g_display.pixel_xres, g_display.pixel_yres); io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); @@ -102,6 +105,10 @@ void ImGui_ImplPlatform_NewFrame() { lastTime = now; } +ImGuiMouseCursor ImGui_ImplPlatform_GetCursor() { + return g_cursor; +} + // Written by chatgpt ImGuiKey KeyCodeToImGui(InputKeyCode keyCode) { switch (keyCode) { diff --git a/ext/imgui/imgui_impl_platform.h b/ext/imgui/imgui_impl_platform.h index 73090114fd52..4f25c192395e 100644 --- a/ext/imgui/imgui_impl_platform.h +++ b/ext/imgui/imgui_impl_platform.h @@ -14,3 +14,5 @@ void ImGui_ImplPlatform_NewFrame(); void ImGui_ImplPlatform_KeyEvent(const KeyInput &key); void ImGui_ImplPlatform_TouchEvent(const TouchInput &touch); void ImGui_ImplPlatform_AxisEvent(const AxisInput &axis); + +ImGuiMouseCursor ImGui_ImplPlatform_GetCursor(); From 374c2e1c14571169ce68a7d83841d091f09bb6c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 13 Dec 2024 22:08:26 +0100 Subject: [PATCH 45/58] Fix sending garbage data after chat message, this time for real --- Core/HLE/proAdhocServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/HLE/proAdhocServer.cpp b/Core/HLE/proAdhocServer.cpp index 4559898304b5..4138155119e8 100644 --- a/Core/HLE/proAdhocServer.cpp +++ b/Core/HLE/proAdhocServer.cpp @@ -1180,7 +1180,7 @@ void spread_message(SceNetAdhocctlUserNode *user, const char *message) } // Chat Packet - SceNetAdhocctlChatPacketS2C packet; + SceNetAdhocctlChatPacketS2C packet{}; // Set Chat Opcode packet.base.base.opcode = OPCODE_CHAT; From 68f61c2adda0be8390113d23e80cc9885782117b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 14 Dec 2024 11:03:18 +0100 Subject: [PATCH 46/58] ImGeDebugger: Highlight changes, show old value on hover --- Common/Render/Text/draw_text.cpp | 3 +++ GPU/Debugger/State.cpp | 3 +++ GPU/Debugger/Stepping.cpp | 31 +++++++++++++------------------ GPU/Debugger/Stepping.h | 3 ++- UI/ImDebugger/ImGe.cpp | 26 +++++++++++++++++--------- 5 files changed, 38 insertions(+), 28 deletions(-) diff --git a/Common/Render/Text/draw_text.cpp b/Common/Render/Text/draw_text.cpp index 1fa2a070b63f..d89a8b080ef7 100644 --- a/Common/Render/Text/draw_text.cpp +++ b/Common/Render/Text/draw_text.cpp @@ -167,6 +167,9 @@ void TextDrawer::MeasureStringRect(std::string_view str, const Bounds &bounds, f } void TextDrawer::DrawStringRect(DrawBuffer &target, std::string_view str, const Bounds &bounds, uint32_t color, int align) { + if (bounds.w < 0.0f || bounds.h < 0.0f) { + return; + } float x = bounds.x; float y = bounds.y; if (align & ALIGN_HCENTER) { diff --git a/GPU/Debugger/State.cpp b/GPU/Debugger/State.cpp index 4398d79768f4..ec4a09378713 100644 --- a/GPU/Debugger/State.cpp +++ b/GPU/Debugger/State.cpp @@ -12,6 +12,9 @@ #include "Core/System.h" void FormatStateRow(GPUDebugInterface *gpudebug, char *dest, size_t destSize, CmdFormatType fmt, u32 value, bool enabled, u32 otherValue, u32 otherValue2) { + value &= 0xFFFFFF; + otherValue &= 0xFFFFFF; + otherValue2 &= 0xFFFFFF; switch (fmt) { case CMD_FMT_HEX: snprintf(dest, destSize, "%06x", value); diff --git a/GPU/Debugger/Stepping.cpp b/GPU/Debugger/Stepping.cpp index b4a0a81d1093..4395a3275a5e 100644 --- a/GPU/Debugger/Stepping.cpp +++ b/GPU/Debugger/Stepping.cpp @@ -71,6 +71,7 @@ static int bufferLevel; static bool lastWasFramebuffer; static u32 pauseSetCmdValue; +// This is used only to highlight differences. Should really be owned by the debugger. static GPUgstate lastGState; const char *PauseActionToString(PauseAction action) { @@ -161,21 +162,6 @@ void WaitForPauseAction() { actionWait.wait(guard); } -static void StartStepping() { - if (lastGState.cmdmem[1] == 0) { - lastGState = gstate; - // Play it safe so we don't keep resetting. - lastGState.cmdmem[1] |= 0x01000000; - } - isStepping = true; - stepCounter++; -} - -static void StopStepping() { - lastGState = gstate; - isStepping = false; -} - bool ProcessStepping() { _dbg_assert_(gpuDebug); @@ -215,7 +201,15 @@ bool EnterStepping() { return false; } - StartStepping(); + // StartStepping + if (lastGState.cmdmem[1] == 0) { + lastGState = gstate; + // Play it safe so we don't keep resetting. + lastGState.cmdmem[1] |= 0x01000000; + } + + isStepping = true; + stepCounter++; // Just to be sure. if (pauseAction == PAUSE_CONTINUE) { @@ -227,7 +221,8 @@ bool EnterStepping() { } void ResumeFromStepping() { - StopStepping(); + lastGState = gstate; + isStepping = false; SetPauseAction(PAUSE_CONTINUE, false); } @@ -300,7 +295,7 @@ bool GPU_FlushDrawing() { return true; } -GPUgstate LastState() { +const GPUgstate &LastState() { return lastGState; } diff --git a/GPU/Debugger/Stepping.h b/GPU/Debugger/Stepping.h index 1393ffc7c74d..0146053a8aa8 100644 --- a/GPU/Debugger/Stepping.h +++ b/GPU/Debugger/Stepping.h @@ -46,5 +46,6 @@ namespace GPUStepping { bool GPU_SetCmdValue(u32 op); bool GPU_FlushDrawing(); - GPUgstate LastState(); + // Can be used to highlight differences in a debugger. + const GPUgstate &LastState(); }; diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index 96a3362ba0df..452f32c13a8f 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -746,7 +746,7 @@ static const StateItem g_vertexState[] = { }; void ImGeStateWindow::Snapshot() { - + // Not needed for now, we have GPUStepping::LastState() } // TODO: Separate window or merge into Ge debugger? @@ -769,6 +769,8 @@ void ImGeStateWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterface bool sectionOpen = false; for (size_t i = 0; i < numRows; i++) { const GECmdInfo &info = GECmdInfoByCmd(rows[i].cmd); + const GPUgstate &lastState = GPUStepping::LastState(); + bool diff = lastState.cmdmem[rows[i].cmd] != gstate.cmdmem[rows[i].cmd]; if (rows[i].header) { anySection = true; @@ -788,31 +790,37 @@ void ImGeStateWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterface } const bool enabled = info.enableCmd == 0 || (gstate.cmdmem[info.enableCmd] & 1) == 1; - if (!enabled) + if (diff) { + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 96, 32, enabled ? 255 : 128)); + } else if (!enabled) { ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 128)); + } if (!rows[i].header) { ImGui::TextUnformatted(info.uiName); ImGui::TableNextColumn(); } if (rows[i].cmd != GE_CMD_NOP) { - char temp[256]; + char temp[128], temp2[128]; - const u32 value = gstate.cmdmem[info.cmd] & 0xFFFFFF; - const u32 otherValue = gstate.cmdmem[info.otherCmd] & 0xFFFFFF; - const u32 otherValue2 = gstate.cmdmem[info.otherCmd2] & 0xFFFFFF; + const u32 value = gstate.cmdmem[info.cmd]; + const u32 otherValue = gstate.cmdmem[info.otherCmd]; - // Special handling for pointer and pointer/width entries + // Special handling for pointer and pointer/width entries - create an address control if (info.fmt == CMD_FMT_PTRWIDTH) { const u32 val = value | (otherValue & 0x00FF0000) << 8; ImClickableAddress(val, control, ImCmd::NONE); ImGui::SameLine(); ImGui::Text("w=%d", otherValue & 0xFFFF); } else { - FormatStateRow(gpuDebug, temp, sizeof(temp), info.fmt, value, true, otherValue, otherValue2); + FormatStateRow(gpuDebug, temp, sizeof(temp), info.fmt, value, true, otherValue, gstate.cmdmem[info.otherCmd2]); ImGui::TextUnformatted(temp); } + if (diff && ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) { + FormatStateRow(gpuDebug, temp2, sizeof(temp2), info.fmt, lastState.cmdmem[info.cmd], true, lastState.cmdmem[info.otherCmd], lastState.cmdmem[info.otherCmd2]); + ImGui::SetTooltip("Previous: %s", temp2); + } } - if (!enabled) + if (diff || !enabled) ImGui::PopStyleColor(); } if (sectionOpen) { From bebd40e6de1ec0b1d776f833900ca3a89cd6fe64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 14 Dec 2024 11:26:47 +0100 Subject: [PATCH 47/58] Split the register window, prepare for better diffs --- UI/ImDebugger/ImDebugger.cpp | 183 +++++++++++++++++++++-------------- UI/ImDebugger/ImDebugger.h | 19 +++- UI/ImDebugger/ImGe.cpp | 2 +- UI/ImDebugger/ImGe.h | 3 + 4 files changed, 130 insertions(+), 77 deletions(-) diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index c7d0a09c8827..2cfde5ef9b1a 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -120,88 +120,104 @@ void DrawSchedulerView(ImConfig &cfg) { ImGui::End(); } -void DrawRegisterView(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug) { +static void DrawGPRs(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug, const ImSnapshotState &prev) { ImGui::SetNextWindowSize(ImVec2(320, 600), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("Registers", &config.regsOpen)) { + if (!ImGui::Begin("MIPS GPRs", &config.vfpuOpen)) { ImGui::End(); return; } - if (ImGui::BeginTabBar("RegisterGroups", ImGuiTabBarFlags_None)) { - if (ImGui::BeginTabItem("GPR")) { - if (ImGui::BeginTable("gpr", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) { - ImGui::TableSetupColumn("regname", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("value", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("value_i", ImGuiTableColumnFlags_WidthStretch); - - auto gprLine = [&](int index, const char *regname, int value) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::TextUnformatted(regname); - ImGui::TableNextColumn(); - if (Memory::IsValid4AlignedAddress(value)) { - ImGui::PushID(index); - ImClickableAddress(value, control, index == MIPS_REG_RA ? ImCmd::SHOW_IN_CPU_DISASM : ImCmd::SHOW_IN_MEMORY_VIEWER); - ImGui::PopID(); - } else { - ImGui::Text("%08x", value); - } - if (value >= -1000000 && value <= 1000000) { - ImGui::TableSetColumnIndex(2); - ImGui::Text("%d", value); - } - }; - for (int i = 0; i < 32; i++) { - gprLine(i, mipsDebug->GetRegName(0, i).c_str(), mipsDebug->GetGPR32Value(i)); - } - gprLine(32, "hi", mipsDebug->GetHi()); - gprLine(33, "lo", mipsDebug->GetLo()); - gprLine(34, "pc", mipsDebug->GetPC()); - gprLine(35, "ll", mipsDebug->GetLLBit()); - ImGui::EndTable(); - } + if (ImGui::BeginTable("gpr", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) { + ImGui::TableSetupColumn("regname", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("value", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("value_i", ImGuiTableColumnFlags_WidthStretch); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("FPU")) { - if (ImGui::BeginTable("fpr", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) { - ImGui::TableSetupColumn("regname", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("value", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("value_i", ImGuiTableColumnFlags_WidthStretch); + auto gprLine = [&](int index, const char *regname, int value, int prevValue) { + bool diff = false; // value != prevValue; + bool disabled = value == 0xdeadbeef; - // fpcond - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::TextUnformatted("fpcond"); - ImGui::TableNextColumn(); - ImGui::Text("%08x", mipsDebug->GetFPCond()); - - for (int i = 0; i < 32; i++) { - float fvalue = mipsDebug->GetFPR32Value(i); - u32 fivalue; - memcpy(&fivalue, &fvalue, sizeof(fivalue)); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::TextUnformatted(mipsDebug->GetRegName(1, i).c_str()); - ImGui::TableNextColumn(); - ImGui::Text("%0.7f", fvalue); - ImGui::TableNextColumn(); - ImGui::Text("%08x", fivalue); - } - - ImGui::EndTable(); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(regname); + ImGui::TableNextColumn(); + if (diff) { + ImGui::PushStyleColor(ImGuiCol_Text, disabled ? ImDebuggerColor_Diff : ImDebuggerColor_DiffAlpha); + } else if (disabled) { + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 128)); } - ImGui::EndTabItem(); + if (Memory::IsValid4AlignedAddress(value)) { + ImGui::PushID(index); + ImClickableAddress(value, control, index == MIPS_REG_RA ? ImCmd::SHOW_IN_CPU_DISASM : ImCmd::SHOW_IN_MEMORY_VIEWER); + ImGui::PopID(); + } else { + ImGui::Text("%08x", value); + } + if (value >= -1000000 && value <= 1000000) { + ImGui::TableSetColumnIndex(2); + ImGui::Text("%d", value); + } + if (diff || disabled) { + ImGui::PopStyleColor(); + } + }; + for (int i = 0; i < 32; i++) { + gprLine(i, mipsDebug->GetRegName(0, i).c_str(), mipsDebug->GetGPR32Value(i), prev.gpr[i]); } - if (ImGui::BeginTabItem("VFPU")) { - ImGui::Text("TODO"); - ImGui::EndTabItem(); + gprLine(32, "hi", mipsDebug->GetHi(), prev.hi); + gprLine(33, "lo", mipsDebug->GetLo(), prev.lo); + gprLine(34, "pc", mipsDebug->GetPC(), prev.pc); + gprLine(35, "ll", mipsDebug->GetLLBit(), prev.ll); + ImGui::EndTable(); + } + ImGui::End(); +} + +static void DrawFPRs(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug, const ImSnapshotState &prev) { + ImGui::SetNextWindowSize(ImVec2(320, 600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("MIPS FPRs", &config.vfpuOpen)) { + ImGui::End(); + return; + } + if (ImGui::BeginTable("fpr", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) { + ImGui::TableSetupColumn("regname", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("value", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("value_i", ImGuiTableColumnFlags_WidthStretch); + + // fpcond + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("fpcond"); + ImGui::TableNextColumn(); + ImGui::Text("%08x", mipsDebug->GetFPCond()); + + for (int i = 0; i < 32; i++) { + float fvalue = mipsDebug->GetFPR32Value(i); + u32 fivalue; + memcpy(&fivalue, &fvalue, sizeof(fivalue)); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(mipsDebug->GetRegName(1, i).c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%0.7f", fvalue); + ImGui::TableNextColumn(); + ImGui::Text("%08x", fivalue); } - ImGui::EndTabBar(); + + ImGui::EndTable(); } ImGui::End(); } +static void DrawVFPU(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug, const ImSnapshotState &prev) { + ImGui::SetNextWindowSize(ImVec2(320, 600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("MIPS FPRs", &config.vfpuOpen)) { + ImGui::End(); + return; + } + ImGui::Text("TODO"); + ImGui::End(); +} + static const char *ThreadStatusToString(u32 status) { switch (status) { case THREADSTATUS_RUNNING: return "Running"; @@ -910,6 +926,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu if (lastCpuStepCount_ != Core_GetSteppingCounter()) { lastCpuStepCount_ = Core_GetSteppingCounter(); + Snapshot(currentMIPS); disasm_.NotifyStep(); } @@ -988,7 +1005,9 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu } if (ImGui::BeginMenu("CPU")) { ImGui::MenuItem("CPU debugger", nullptr, &cfg_.disasmOpen); - ImGui::MenuItem("Registers", nullptr, &cfg_.regsOpen); + ImGui::MenuItem("GPR regs", nullptr, &cfg_.gprOpen); + ImGui::MenuItem("FPR regs", nullptr, &cfg_.fprOpen); + ImGui::MenuItem("VFPU regs", nullptr, &cfg_.vfpuOpen); ImGui::MenuItem("Callstacks", nullptr, &cfg_.callstackOpen); ImGui::MenuItem("Breakpoints", nullptr, &cfg_.breakpointsOpen); ImGui::MenuItem("Scheduler", nullptr, &cfg_.schedulerOpen); @@ -1049,8 +1068,16 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu disasm_.Draw(mipsDebug, cfg_, control, coreState); } - if (cfg_.regsOpen) { - DrawRegisterView(cfg_, control, mipsDebug); + if (cfg_.gprOpen) { + DrawGPRs(cfg_, control, mipsDebug, snapshot_); + } + + if (cfg_.fprOpen) { + DrawFPRs(cfg_, control, mipsDebug, snapshot_); + } + + if (cfg_.vfpuOpen) { + DrawVFPU(cfg_, control, mipsDebug, snapshot_); } if (cfg_.breakpointsOpen) { @@ -1156,8 +1183,14 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu } } -void ImDebugger::Snapshot() { - +void ImDebugger::Snapshot(MIPSState *mips) { + memcpy(snapshot_.gpr, mips->r, sizeof(snapshot_.gpr)); + memcpy(snapshot_.fpr, mips->fs, sizeof(snapshot_.fpr)); + memcpy(snapshot_.vpr, mips->v, sizeof(snapshot_.vpr)); + snapshot_.pc = mips->pc; + snapshot_.lo = mips->lo; + snapshot_.hi = mips->hi; + snapshot_.ll = mips->llBit; } void ImMemWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl &control, int index) { @@ -1450,7 +1483,9 @@ void ImConfig::SyncConfig(IniFile *ini, bool save) { sync.SetSection(ini->GetOrCreateSection("Windows")); sync.Sync("disasmOpen", &disasmOpen, true); sync.Sync("demoOpen ", &demoOpen, false); - sync.Sync("regsOpen", ®sOpen, true); + sync.Sync("gprOpen", &gprOpen, false); + sync.Sync("fprOpen", &fprOpen, false); + sync.Sync("vfpuOpen", &vfpuOpen, false); sync.Sync("threadsOpen", &threadsOpen, false); sync.Sync("callstackOpen", &callstackOpen, false); sync.Sync("breakpointsOpen", &breakpointsOpen, false); diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h index 62bd71b49c97..85b9a626ab0b 100644 --- a/UI/ImDebugger/ImDebugger.h +++ b/UI/ImDebugger/ImDebugger.h @@ -99,12 +99,25 @@ class ImMemWindow { u32 gotoAddr_ = 0x08800000; }; +// Snapshot of the MIPS CPU and other things we want to show diffs off. +struct ImSnapshotState { + u32 gpr[32]; + float fpr[32]; + float vpr[128]; + u32 pc; + u32 lo; + u32 hi; + u32 ll; +}; + struct ImConfig { // Defaults for saved settings are set in SyncConfig. bool disasmOpen; bool demoOpen; - bool regsOpen; + bool gprOpen; + bool fprOpen; + bool vfpuOpen; bool threadsOpen; bool callstackOpen; bool breakpointsOpen; @@ -173,7 +186,7 @@ class ImDebugger { // Should be called just before starting a step or run, so that things can // save state that they can later compare with, to highlight changes. - void Snapshot(); + void Snapshot(MIPSState *mips); private: Path ConfigPath(); @@ -186,6 +199,8 @@ class ImDebugger { ImMemWindow mem_[4]; // We support 4 separate instances of the memory viewer. ImStructViewer structViewer_; + ImSnapshotState snapshot_; + int lastCpuStepCount_ = -1; int lastGpuStepCount_ = -1; diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index 452f32c13a8f..33638556782c 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -791,7 +791,7 @@ void ImGeStateWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterface const bool enabled = info.enableCmd == 0 || (gstate.cmdmem[info.enableCmd] & 1) == 1; if (diff) { - ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 96, 32, enabled ? 255 : 128)); + ImGui::PushStyleColor(ImGuiCol_Text, enabled ? ImDebuggerColor_Diff : ImDebuggerColor_DiffAlpha); } else if (!enabled) { ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 128)); } diff --git a/UI/ImDebugger/ImGe.h b/UI/ImDebugger/ImGe.h index fda9d3e2bd52..895ba8964579 100644 --- a/UI/ImDebugger/ImGe.h +++ b/UI/ImDebugger/ImGe.h @@ -10,6 +10,9 @@ struct ImControl; class FramebufferManagerCommon; class TextureCacheCommon; +constexpr ImU32 ImDebuggerColor_Diff = IM_COL32(255, 96, 32, 255); +constexpr ImU32 ImDebuggerColor_DiffAlpha = IM_COL32(255, 96, 32, 128); + void DrawFramebuffersWindow(ImConfig &cfg, FramebufferManagerCommon *framebufferManager); void DrawTexturesWindow(ImConfig &cfg, TextureCacheCommon *textureCache); void DrawDisplayWindow(ImConfig &cfg, FramebufferManagerCommon *framebufferManager); From be70fdf0076cbcc5a99da8337bc325c76a376aca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 14 Dec 2024 19:24:10 +0100 Subject: [PATCH 48/58] Better diff tracking in GPR window --- UI/ImDebugger/ImDebugger.cpp | 24 +++++++++++++++--------- UI/ImDebugger/ImDebugger.h | 1 + 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 2cfde5ef9b1a..168c8f6ff037 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -127,13 +127,15 @@ static void DrawGPRs(ImConfig &config, ImControl &control, const MIPSDebugInterf return; } + bool noDiff = coreState == CORE_RUNNING_CPU; + if (ImGui::BeginTable("gpr", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) { ImGui::TableSetupColumn("regname", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("value", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("value_i", ImGuiTableColumnFlags_WidthStretch); auto gprLine = [&](int index, const char *regname, int value, int prevValue) { - bool diff = false; // value != prevValue; + bool diff = value != prevValue && !noDiff; bool disabled = value == 0xdeadbeef; ImGui::TableNextRow(); @@ -141,7 +143,7 @@ static void DrawGPRs(ImConfig &config, ImControl &control, const MIPSDebugInterf ImGui::TextUnformatted(regname); ImGui::TableNextColumn(); if (diff) { - ImGui::PushStyleColor(ImGuiCol_Text, disabled ? ImDebuggerColor_Diff : ImDebuggerColor_DiffAlpha); + ImGui::PushStyleColor(ImGuiCol_Text, !disabled ? ImDebuggerColor_Diff : ImDebuggerColor_DiffAlpha); } else if (disabled) { ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 128)); } @@ -178,6 +180,9 @@ static void DrawFPRs(ImConfig &config, ImControl &control, const MIPSDebugInterf ImGui::End(); return; } + + bool noDiff = coreState == CORE_RUNNING_CPU; + if (ImGui::BeginTable("fpr", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) { ImGui::TableSetupColumn("regname", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("value", ImGuiTableColumnFlags_WidthFixed); @@ -926,6 +931,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu if (lastCpuStepCount_ != Core_GetSteppingCounter()) { lastCpuStepCount_ = Core_GetSteppingCounter(); + snapshot_ = newSnapshot_; // Compare against the previous snapshot. Snapshot(currentMIPS); disasm_.NotifyStep(); } @@ -1184,13 +1190,13 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu } void ImDebugger::Snapshot(MIPSState *mips) { - memcpy(snapshot_.gpr, mips->r, sizeof(snapshot_.gpr)); - memcpy(snapshot_.fpr, mips->fs, sizeof(snapshot_.fpr)); - memcpy(snapshot_.vpr, mips->v, sizeof(snapshot_.vpr)); - snapshot_.pc = mips->pc; - snapshot_.lo = mips->lo; - snapshot_.hi = mips->hi; - snapshot_.ll = mips->llBit; + memcpy(newSnapshot_.gpr, mips->r, sizeof(newSnapshot_.gpr)); + memcpy(newSnapshot_.fpr, mips->fs, sizeof(newSnapshot_.fpr)); + memcpy(newSnapshot_.vpr, mips->v, sizeof(newSnapshot_.vpr)); + newSnapshot_.pc = mips->pc; + newSnapshot_.lo = mips->lo; + newSnapshot_.hi = mips->hi; + newSnapshot_.ll = mips->llBit; } void ImMemWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl &control, int index) { diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h index 85b9a626ab0b..8b2e16739911 100644 --- a/UI/ImDebugger/ImDebugger.h +++ b/UI/ImDebugger/ImDebugger.h @@ -199,6 +199,7 @@ class ImDebugger { ImMemWindow mem_[4]; // We support 4 separate instances of the memory viewer. ImStructViewer structViewer_; + ImSnapshotState newSnapshot_; ImSnapshotState snapshot_; int lastCpuStepCount_ = -1; From d0cb7ee1070515e09ba304703bebc66ebd19761c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 15 Dec 2024 00:07:29 +0100 Subject: [PATCH 49/58] Diff changes in FPR registers too. --- UI/ImDebugger/ImDebugger.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 168c8f6ff037..e50577638f79 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -176,7 +176,7 @@ static void DrawGPRs(ImConfig &config, ImControl &control, const MIPSDebugInterf static void DrawFPRs(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug, const ImSnapshotState &prev) { ImGui::SetNextWindowSize(ImVec2(320, 600), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("MIPS FPRs", &config.vfpuOpen)) { + if (!ImGui::Begin("MIPS FPRs", &config.fprOpen)) { ImGui::End(); return; } @@ -197,15 +197,26 @@ static void DrawFPRs(ImConfig &config, ImControl &control, const MIPSDebugInterf for (int i = 0; i < 32; i++) { float fvalue = mipsDebug->GetFPR32Value(i); + float prevValue = prev.fpr[i]; + + // NOTE: Using memcmp to avoid NaN problems. + bool diff = memcmp(&fvalue, &prevValue, 4) != 0 && !noDiff; + u32 fivalue; memcpy(&fivalue, &fvalue, sizeof(fivalue)); ImGui::TableNextRow(); ImGui::TableNextColumn(); + if (diff) { + ImGui::PushStyleColor(ImGuiCol_Text, ImDebuggerColor_Diff); + } ImGui::TextUnformatted(mipsDebug->GetRegName(1, i).c_str()); ImGui::TableNextColumn(); ImGui::Text("%0.7f", fvalue); ImGui::TableNextColumn(); ImGui::Text("%08x", fivalue); + if (diff) { + ImGui::PopStyleColor(); + } } ImGui::EndTable(); @@ -215,7 +226,7 @@ static void DrawFPRs(ImConfig &config, ImControl &control, const MIPSDebugInterf static void DrawVFPU(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug, const ImSnapshotState &prev) { ImGui::SetNextWindowSize(ImVec2(320, 600), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("MIPS FPRs", &config.vfpuOpen)) { + if (!ImGui::Begin("MIPS VFPU regs", &config.vfpuOpen)) { ImGui::End(); return; } From 2fe5642156d1e2f0d7d077bcc5d410cb7451e08e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 15 Dec 2024 00:11:07 +0100 Subject: [PATCH 50/58] Some polish --- UI/ImDebugger/ImDebugger.cpp | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index e50577638f79..b31c13822b26 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -127,18 +127,19 @@ static void DrawGPRs(ImConfig &config, ImControl &control, const MIPSDebugInterf return; } - bool noDiff = coreState == CORE_RUNNING_CPU; + bool noDiff = coreState == CORE_RUNNING_CPU || coreState == CORE_STEPPING_GE; if (ImGui::BeginTable("gpr", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) { - ImGui::TableSetupColumn("regname", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("value", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("value_i", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Reg", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Decimal", ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableHeadersRow(); auto gprLine = [&](int index, const char *regname, int value, int prevValue) { bool diff = value != prevValue && !noDiff; bool disabled = value == 0xdeadbeef; - ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextUnformatted(regname); ImGui::TableNextColumn(); @@ -154,8 +155,8 @@ static void DrawGPRs(ImConfig &config, ImControl &control, const MIPSDebugInterf } else { ImGui::Text("%08x", value); } + ImGui::TableNextColumn(); if (value >= -1000000 && value <= 1000000) { - ImGui::TableSetColumnIndex(2); ImGui::Text("%d", value); } if (diff || disabled) { @@ -163,11 +164,16 @@ static void DrawGPRs(ImConfig &config, ImControl &control, const MIPSDebugInterf } }; for (int i = 0; i < 32; i++) { + ImGui::TableNextRow(); gprLine(i, mipsDebug->GetRegName(0, i).c_str(), mipsDebug->GetGPR32Value(i), prev.gpr[i]); } + ImGui::TableNextRow(); gprLine(32, "hi", mipsDebug->GetHi(), prev.hi); + ImGui::TableNextRow(); gprLine(33, "lo", mipsDebug->GetLo(), prev.lo); + ImGui::TableNextRow(); gprLine(34, "pc", mipsDebug->GetPC(), prev.pc); + ImGui::TableNextRow(); gprLine(35, "ll", mipsDebug->GetLLBit(), prev.ll); ImGui::EndTable(); } @@ -181,12 +187,14 @@ static void DrawFPRs(ImConfig &config, ImControl &control, const MIPSDebugInterf return; } - bool noDiff = coreState == CORE_RUNNING_CPU; + bool noDiff = coreState == CORE_RUNNING_CPU || coreState == CORE_STEPPING_GE; if (ImGui::BeginTable("fpr", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) { - ImGui::TableSetupColumn("regname", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("value", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("value_i", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Reg", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Hex", ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableHeadersRow(); // fpcond ImGui::TableNextRow(); From 15b63aa812b604064334ba63b90bb679af73b1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 15 Dec 2024 00:17:26 +0100 Subject: [PATCH 51/58] Run the imgui processing early in the frame, only do the rendering late --- UI/EmuScreen.cpp | 15 ++++++++++++++- UI/EmuScreen.h | 1 + UI/ImDebugger/ImDebugger.cpp | 3 +++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 6725eda50499..687bf3256b05 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -1539,6 +1539,11 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) { } } + // Running it early allows things like direct readbacks of buffers, things we can't do + // when we have started the final render pass. Well, technically we probably could with some manipulation + // of pass order in the render managers.. + runImDebugger(); + bool blockedExecution = Achievements::IsBlockingExecution(); uint32_t clearColor = 0; if (!blockedExecution) { @@ -1655,7 +1660,7 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) { return flags; } -void EmuScreen::renderImDebugger() { +void EmuScreen::runImDebugger() { if (g_Config.bShowImDebugger) { Draw::DrawContext *draw = screenManager()->getDrawContext(); if (!imguiInited_) { @@ -1691,6 +1696,14 @@ void EmuScreen::renderImDebugger() { imDebugger_->Frame(currentDebugMIPS, gpuDebug); ImGui::Render(); + } + } +} + +void EmuScreen::renderImDebugger() { + if (g_Config.bShowImDebugger) { + Draw::DrawContext *draw = screenManager()->getDrawContext(); + if (PSP_IsInited()) { ImGui_ImplThin3d_RenderDrawData(ImGui::GetDrawData(), draw); } } diff --git a/UI/EmuScreen.h b/UI/EmuScreen.h index 0947bc2d565b..2fc7335b58c1 100644 --- a/UI/EmuScreen.h +++ b/UI/EmuScreen.h @@ -79,6 +79,7 @@ class EmuScreen : public UIScreen { void bootComplete(); bool hasVisibleUI(); void renderUI(); + void runImDebugger(); void renderImDebugger(); void onVKey(int virtualKeyCode, bool down); diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index b31c13822b26..2953a66a6c3c 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -65,6 +65,9 @@ void ShowInWindowMenuItems(uint32_t addr, ImControl &control) { } void StatusBar(std::string_view status) { + if (!status.size()) { + return; + } ImGui::TextUnformatted(status.data(), status.data() + status.length()); ImGui::SameLine(); if (ImGui::SmallButton("Copy")) { From 6c355836da536a18e9c0c3e698835606ca2410ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 15 Dec 2024 00:40:33 +0100 Subject: [PATCH 52/58] ImGeDebugger: Implement draw stepping --- GPU/Debugger/Debugger.cpp | 34 ++++++++++++++++++++------------- ext/imgui/imgui_impl_thin3d.cpp | 4 ++++ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/GPU/Debugger/Debugger.cpp b/GPU/Debugger/Debugger.cpp index ed1729c67990..fdad58cda158 100644 --- a/GPU/Debugger/Debugger.cpp +++ b/GPU/Debugger/Debugger.cpp @@ -36,7 +36,7 @@ static int primsLastFrame = 0; static int primsThisFrame = 0; static int thisFlipNum = 0; -bool g_drawNotified = false; +bool g_primAfterDraw = false; static double lastStepTime = -1.0; static uint32_t g_skipPcOnce = 0; @@ -79,6 +79,15 @@ void SetActive(bool flag) { GPUStepping::ResumeFromStepping(); lastStepTime = -1.0; } + /* + active = false; + breakAtCount = -1; + hasBreakpoints = false; + thisFlipNum = 0; + g_primAfterDraw = false; + lastStepTime = -1.0f; + g_skipPcOnce = false; + */ } bool IsActive() { @@ -103,7 +112,12 @@ void SetBreakNext(BreakNext next) { } else if (next == BreakNext::CURVE) { GPUBreakpoints::AddCmdBreakpoint(GE_CMD_BEZIER, true); GPUBreakpoints::AddCmdBreakpoint(GE_CMD_SPLINE, true); + } else if (next == BreakNext::DRAW) { + // This is now handled by switching to BreakNext::PRIM when we encounter a flush. + // This will take us to the following actual draw. + g_primAfterDraw = true; } + if (GPUStepping::IsStepping()) { GPUStepping::ResumeFromStepping(); } @@ -124,12 +138,6 @@ NotifyResult NotifyCommand(u32 pc) { return NotifyResult::Skip; // return false } - // Hack to handle draw notifications, that don't come in via NotifyCommand. - if (g_drawNotified) { - g_drawNotified = false; - return NotifyResult::Break; - } - u32 op = Memory::ReadUnchecked_U32(pc); u32 cmd = op >> 24; if (thisFlipNum != gpuStats.numFlips) { @@ -198,13 +206,13 @@ void NotifyDraw() { if (!active) return; if (breakNext == BreakNext::DRAW && !GPUStepping::IsStepping()) { - if (lastStepTime >= 0.0) { - NOTICE_LOG(Log::GeDebugger, "Waiting at a draw (%fms)", (time_now_d() - lastStepTime) * 1000.0); - lastStepTime = -1.0; - } else { - NOTICE_LOG(Log::GeDebugger, "Waiting at a draw"); + // Hack to handle draw notifications, that don't come in via NotifyCommand. + if (g_primAfterDraw) { + NOTICE_LOG(Log::GeDebugger, "Flush detected, breaking at next PRIM"); + g_primAfterDraw = false; + // Switch to PRIM mode. + SetBreakNext(BreakNext::PRIM); } - g_drawNotified = true; } } diff --git a/ext/imgui/imgui_impl_thin3d.cpp b/ext/imgui/imgui_impl_thin3d.cpp index ab7b561546ba..4b1e90d69534 100644 --- a/ext/imgui/imgui_impl_thin3d.cpp +++ b/ext/imgui/imgui_impl_thin3d.cpp @@ -49,6 +49,10 @@ static BackendData *ImGui_ImplThin3d_GetBackendData() { // Render function void ImGui_ImplThin3d_RenderDrawData(ImDrawData* draw_data, Draw::DrawContext *draw) { + if (!draw_data) { + // Possible race condition. + return; + } // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); From 7b3687d79c16e4e102fa8151625e22c3ece10f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 15 Dec 2024 10:49:49 +0100 Subject: [PATCH 53/58] Replace a fiddly callback with a global bool (which later won't be global) --- GPU/Debugger/Breakpoints.cpp | 37 +++++++++++++++++------------------- GPU/Debugger/Breakpoints.h | 5 ++++- GPU/Debugger/Debugger.cpp | 16 +--------------- 3 files changed, 22 insertions(+), 36 deletions(-) diff --git a/GPU/Debugger/Breakpoints.cpp b/GPU/Debugger/Breakpoints.cpp index e4e521404b1b..debc260bba27 100644 --- a/GPU/Debugger/Breakpoints.cpp +++ b/GPU/Debugger/Breakpoints.cpp @@ -29,9 +29,6 @@ namespace GPUBreakpoints { -static void NothingToDo(bool) { -} - struct BreakpointInfo { bool isConditional = false; PostfixExpression expression; @@ -48,7 +45,6 @@ static std::set breakRenderTargets; static size_t breakPCsCount = 0; static size_t breakTexturesCount = 0; static size_t breakRenderTargetsCount = 0; -static std::function notifyBreakpoints = &NothingToDo; // If these are set, the above are also, but they should be temporary. static bool breakCmdsTemp[256]; @@ -57,6 +53,8 @@ static std::set breakTexturesTemp; static std::set breakRenderTargetsTemp; static bool textureChangeTemp = false; +bool g_hasBreakpoints; + static u32 lastTexture = 0xFFFFFFFF; // These are commands we run before breaking on a texture. @@ -76,8 +74,7 @@ const static u8 textureRelatedCmds[] = { }; static std::vector nonTextureCmds; -void Init(void (*hasBreakpoints)(bool flag)) { - notifyBreakpoints = hasBreakpoints; +void Init() { ClearAllBreakpoints(); nonTextureCmds.clear(); @@ -350,7 +347,7 @@ void AddAddressBreakpoint(u32 addr, bool temp) { } breakPCsCount = breakPCs.size(); - notifyBreakpoints(true); + g_hasBreakpoints = true; } void AddCmdBreakpoint(u8 cmd, bool temp) { @@ -369,7 +366,7 @@ void AddCmdBreakpoint(u8 cmd, bool temp) { breakCmdsInfo[cmd].isConditional = false; } } - notifyBreakpoints(true); + g_hasBreakpoints = true; } void AddTextureBreakpoint(u32 addr, bool temp) { @@ -386,7 +383,7 @@ void AddTextureBreakpoint(u32 addr, bool temp) { } breakTexturesCount = breakTextures.size(); - notifyBreakpoints(true); + g_hasBreakpoints = true; } void AddRenderTargetBreakpoint(u32 addr, bool temp) { @@ -405,19 +402,19 @@ void AddRenderTargetBreakpoint(u32 addr, bool temp) { } breakRenderTargetsCount = breakRenderTargets.size(); - notifyBreakpoints(true); + g_hasBreakpoints = true; } void AddTextureChangeTempBreakpoint() { textureChangeTemp = true; - notifyBreakpoints(true); + g_hasBreakpoints = true; } void AddAnyTempBreakpoint() { for (int i = 0; i < 256; ++i) { AddCmdBreakpoint(i, true); } - notifyBreakpoints(true); + g_hasBreakpoints = true; } void RemoveAddressBreakpoint(u32 addr) { @@ -427,7 +424,7 @@ void RemoveAddressBreakpoint(u32 addr) { breakPCs.erase(addr); breakPCsCount = breakPCs.size(); - notifyBreakpoints(HasAnyBreakpoints()); + g_hasBreakpoints = HasAnyBreakpoints(); } void RemoveCmdBreakpoint(u8 cmd) { @@ -435,7 +432,7 @@ void RemoveCmdBreakpoint(u8 cmd) { breakCmdsTemp[cmd] = false; breakCmds[cmd] = false; - notifyBreakpoints(HasAnyBreakpoints()); + g_hasBreakpoints = HasAnyBreakpoints(); } void RemoveTextureBreakpoint(u32 addr) { @@ -445,7 +442,7 @@ void RemoveTextureBreakpoint(u32 addr) { breakTextures.erase(addr); breakTexturesCount = breakTextures.size(); - notifyBreakpoints(HasAnyBreakpoints()); + g_hasBreakpoints = HasAnyBreakpoints(); } void RemoveRenderTargetBreakpoint(u32 addr) { @@ -457,14 +454,14 @@ void RemoveRenderTargetBreakpoint(u32 addr) { breakRenderTargets.erase(addr); breakRenderTargetsCount = breakRenderTargets.size(); - notifyBreakpoints(HasAnyBreakpoints()); + g_hasBreakpoints = HasAnyBreakpoints(); } void RemoveTextureChangeTempBreakpoint() { std::lock_guard guard(breaksLock); textureChangeTemp = false; - notifyBreakpoints(HasAnyBreakpoints()); + g_hasBreakpoints = HasAnyBreakpoints(); } static bool SetupCond(BreakpointInfo &bp, const std::string &expression, std::string *error) { @@ -548,7 +545,7 @@ void ClearAllBreakpoints() { breakRenderTargetsCount = breakRenderTargets.size(); textureChangeTemp = false; - notifyBreakpoints(false); + g_hasBreakpoints = false; } void ClearTempBreakpoints() { @@ -581,7 +578,7 @@ void ClearTempBreakpoints() { breakRenderTargetsCount = breakRenderTargets.size(); textureChangeTemp = false; - notifyBreakpoints(HasAnyBreakpoints()); + g_hasBreakpoints = HasAnyBreakpoints(); } -}; +} // namespace diff --git a/GPU/Debugger/Breakpoints.h b/GPU/Debugger/Breakpoints.h index cdb3215abf86..90bf5df08351 100644 --- a/GPU/Debugger/Breakpoints.h +++ b/GPU/Debugger/Breakpoints.h @@ -21,7 +21,10 @@ #include "Common/CommonTypes.h" namespace GPUBreakpoints { - void Init(void (*hasBreakpoints)(bool flag)); + +extern bool g_hasBreakpoints; + + void Init(); bool IsBreakpoint(u32 pc, u32 op); diff --git a/GPU/Debugger/Debugger.cpp b/GPU/Debugger/Debugger.cpp index fdad58cda158..740f47e612ba 100644 --- a/GPU/Debugger/Debugger.cpp +++ b/GPU/Debugger/Debugger.cpp @@ -27,10 +27,8 @@ namespace GPUDebug { static bool active = false; -static bool inited = false; static BreakNext breakNext = BreakNext::NONE; static int breakAtCount = -1; -static bool hasBreakpoints = false; static int primsLastFrame = 0; static int primsThisFrame = 0; @@ -60,18 +58,7 @@ const char *BreakNextToString(BreakNext next) { } } -static void Init() { - if (!inited) { - GPUBreakpoints::Init([](bool flag) { - hasBreakpoints = flag; - }); - inited = true; - } -} - void SetActive(bool flag) { - Init(); - active = flag; if (!active) { breakNext = BreakNext::NONE; @@ -82,7 +69,6 @@ void SetActive(bool flag) { /* active = false; breakAtCount = -1; - hasBreakpoints = false; thisFlipNum = 0; g_primAfterDraw = false; lastStepTime = -1.0f; @@ -166,7 +152,7 @@ NotifyResult NotifyCommand(u32 pc) { isBreakpoint = true; } else if (breakNext == BreakNext::COUNT) { isBreakpoint = primsThisFrame == breakAtCount; - } else if (hasBreakpoints) { + } else if (GPUBreakpoints::g_hasBreakpoints) { isBreakpoint = GPUBreakpoints::IsBreakpoint(pc, op); } From 9e019ae24635d4417a90f4311725c60309fb6cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 15 Dec 2024 11:11:59 +0100 Subject: [PATCH 54/58] Remove the notion of the GPU debugger being "Active". Now it's automatic. --- GPU/D3D11/DrawEngineD3D11.cpp | 2 +- GPU/Debugger/Debugger.cpp | 46 +++++++------------------------ GPU/Debugger/Debugger.h | 5 ++-- GPU/Directx9/DrawEngineDX9.cpp | 2 +- GPU/GLES/DrawEngineGLES.cpp | 2 +- GPU/GPUCommon.cpp | 13 +++++---- GPU/GPUCommon.h | 7 +++-- GPU/Software/TransformUnit.cpp | 2 +- GPU/Vulkan/DrawEngineVulkan.cpp | 2 +- Windows/GEDebugger/GEDebugger.cpp | 22 ++++++--------- 10 files changed, 36 insertions(+), 67 deletions(-) diff --git a/GPU/D3D11/DrawEngineD3D11.cpp b/GPU/D3D11/DrawEngineD3D11.cpp index f941e4d17074..e1cfacdc90a8 100644 --- a/GPU/D3D11/DrawEngineD3D11.cpp +++ b/GPU/D3D11/DrawEngineD3D11.cpp @@ -485,7 +485,7 @@ void DrawEngineD3D11::DoFlush() { ResetAfterDrawInline(); framebufferManager_->SetColorUpdated(gstate_c.skipDrawReason); - GPUDebug::NotifyDraw(); + GPUDebug::NotifyFlush(); } TessellationDataTransferD3D11::TessellationDataTransferD3D11(ID3D11DeviceContext *context, ID3D11Device *device) diff --git a/GPU/Debugger/Debugger.cpp b/GPU/Debugger/Debugger.cpp index 740f47e612ba..e7dee230e7c2 100644 --- a/GPU/Debugger/Debugger.cpp +++ b/GPU/Debugger/Debugger.cpp @@ -26,7 +26,6 @@ namespace GPUDebug { -static bool active = false; static BreakNext breakNext = BreakNext::NONE; static int breakAtCount = -1; @@ -58,26 +57,15 @@ const char *BreakNextToString(BreakNext next) { } } -void SetActive(bool flag) { - active = flag; - if (!active) { - breakNext = BreakNext::NONE; - breakAtCount = -1; - GPUStepping::ResumeFromStepping(); - lastStepTime = -1.0; - } - /* - active = false; - breakAtCount = -1; - thisFlipNum = 0; - g_primAfterDraw = false; - lastStepTime = -1.0f; - g_skipPcOnce = false; - */ +bool NeedsSlowInterpreter() { + return breakNext != BreakNext::NONE || GPUBreakpoints::g_hasBreakpoints; } -bool IsActive() { - return active; +void ClearBreak() { + breakNext = BreakNext::NONE; + breakAtCount = -1; + GPUStepping::ResumeFromStepping(); + lastStepTime = -1.0; } BreakNext GetBreakNext() { @@ -85,7 +73,6 @@ BreakNext GetBreakNext() { } void SetBreakNext(BreakNext next) { - SetActive(true); breakNext = next; breakAtCount = -1; if (next == BreakNext::TEX) { @@ -119,11 +106,6 @@ void SetBreakCount(int c, bool relative) { } NotifyResult NotifyCommand(u32 pc) { - if (!active) { - _dbg_assert_(false); - return NotifyResult::Skip; // return false - } - u32 op = Memory::ReadUnchecked_U32(pc); u32 cmd = op >> 24; if (thisFlipNum != gpuStats.numFlips) { @@ -187,12 +169,9 @@ NotifyResult NotifyCommand(u32 pc) { return process ? NotifyResult::Execute : NotifyResult::Skip; } -// TODO: This mechanism is a bit hacky. -void NotifyDraw() { - if (!active) - return; +void NotifyFlush() { if (breakNext == BreakNext::DRAW && !GPUStepping::IsStepping()) { - // Hack to handle draw notifications, that don't come in via NotifyCommand. + // Break on the first PRIM after a flush. if (g_primAfterDraw) { NOTICE_LOG(Log::GeDebugger, "Flush detected, breaking at next PRIM"); g_primAfterDraw = false; @@ -203,17 +182,13 @@ void NotifyDraw() { } void NotifyDisplay(u32 framebuf, u32 stride, int format) { - if (!active) - return; if (breakNext == BreakNext::FRAME) { - // This should work fine, start stepping at the first op of the new frame. + // Start stepping at the first op of the new frame. breakNext = BreakNext::OP; } } void NotifyBeginFrame() { - if (!active) - return; if (breakNext == BreakNext::VSYNC) { // Just start stepping as soon as we can once the vblank finishes. breakNext = BreakNext::OP; @@ -238,7 +213,6 @@ static bool ParseRange(const std::string &s, std::pair &range) { } bool SetRestrictPrims(const char *rule) { - SetActive(true); if (rule == nullptr || rule[0] == 0 || (rule[0] == '*' && rule[1] == 0)) { restrictPrimRanges.clear(); restrictPrimRule.clear(); diff --git a/GPU/Debugger/Debugger.h b/GPU/Debugger/Debugger.h index d7cfb7a7bf9e..1c19b9b88570 100644 --- a/GPU/Debugger/Debugger.h +++ b/GPU/Debugger/Debugger.h @@ -34,8 +34,7 @@ enum class BreakNext { COUNT, }; -void SetActive(bool flag); -bool IsActive(); +bool NeedsSlowInterpreter(); void SetBreakNext(BreakNext next); void SetBreakCount(int c, bool relative = false); @@ -50,7 +49,7 @@ enum class NotifyResult { // While debugging is active, these may block. NotifyResult NotifyCommand(u32 pc); -void NotifyDraw(); +void NotifyFlush(); void NotifyDisplay(u32 framebuf, u32 stride, int format); void NotifyBeginFrame(); diff --git a/GPU/Directx9/DrawEngineDX9.cpp b/GPU/Directx9/DrawEngineDX9.cpp index 6b8e8bdca002..50f830b385d4 100644 --- a/GPU/Directx9/DrawEngineDX9.cpp +++ b/GPU/Directx9/DrawEngineDX9.cpp @@ -421,7 +421,7 @@ void DrawEngineDX9::DoFlush() { ResetAfterDrawInline(); framebufferManager_->SetColorUpdated(gstate_c.skipDrawReason); - GPUDebug::NotifyDraw(); + GPUDebug::NotifyFlush(); } void TessellationDataTransferDX9::SendDataToShader(const SimpleVertex *const *points, int size_u, int size_v, u32 vertType, const Spline::Weight2D &weights) { diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp index 0e3090752b1e..08df5d630bd4 100644 --- a/GPU/GLES/DrawEngineGLES.cpp +++ b/GPU/GLES/DrawEngineGLES.cpp @@ -451,7 +451,7 @@ void DrawEngineGLES::DoFlush() { bail: ResetAfterDrawInline(); framebufferManager_->SetColorUpdated(gstate_c.skipDrawReason); - GPUDebug::NotifyDraw(); + GPUDebug::NotifyFlush(); } // TODO: Refactor this to a single USE flag. diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index ec29fbad3214..2a3b5d7d4773 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -791,7 +791,8 @@ DLResult GPUCommon::ProcessDLQueue() { gpuState = list.pc == list.stall ? GPUSTATE_STALL : GPUSTATE_RUNNING; // To enable breakpoints, we don't do fast matrix loads while debugger active. - debugRecording_ = GPUDebug::IsActive() || GPURecord::IsActive(); + debugRecording_ = GPURecord::IsActive(); + useFastRunLoop_ = !(dumpThisFrame_ || debugRecording_ || GPUDebug::NeedsSlowInterpreter()); } else { resumingFromDebugBreak_ = false; // The bottom part of the gpuState loop below, that wasn't executed @@ -804,7 +805,7 @@ DLResult GPUCommon::ProcessDLQueue() { // Proceed... } - const bool useFastRunLoop = !dumpThisFrame_ && !debugRecording_; + const bool useFastRunLoop = useFastRunLoop_; while (gpuState == GPUSTATE_RUNNING) { if (list.pc == list.stall) { @@ -880,9 +881,9 @@ DLResult GPUCommon::ProcessDLQueue() { } bool GPUCommon::ShouldSplitOverGe() const { - // Check for debugger active, etc. + // Check for debugger active. // We only need to do this if we want to be able to step through Ge display lists using the Ge debuggers. - return GPUDebug::IsActive() || g_Config.bShowImDebugger; + return GPUDebug::NeedsSlowInterpreter(); } void GPUCommon::Execute_OffsetAddr(u32 op, u32 diff) { @@ -948,8 +949,8 @@ void GPUCommon::DoExecuteCall(u32 target) { DisplayList *currentList = this->currentList; // Bone matrix optimization - many games will CALL a bone matrix (!). - // We don't optimize during recording - so the matrix data gets recorded. - if (!debugRecording_ && Memory::IsValidRange(target, 13 * 4) && (Memory::ReadUnchecked_U32(target) >> 24) == GE_CMD_BONEMATRIXDATA) { + // We don't optimize during recording or debugging - so the matrix data gets recorded. + if (useFastRunLoop_ && Memory::IsValidRange(target, 13 * 4) && (Memory::ReadUnchecked_U32(target) >> 24) == GE_CMD_BONEMATRIXDATA) { // Check for the end if ((Memory::ReadUnchecked_U32(target + 11 * 4) >> 24) == GE_CMD_BONEMATRIXDATA && (Memory::ReadUnchecked_U32(target + 12 * 4) >> 24) == GE_CMD_RET && diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index cf442ed61c37..4bdfcaf7a0ad 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -447,7 +447,7 @@ class GPUCommon : public GPUDebugInterface { bool interruptRunning = false; GPURunState gpuState = GPUSTATE_RUNNING; - bool isbreak; + bool isbreak; // This doesn't mean debugger breakpoints. u64 drawCompleteTicks; u64 busyTicks; @@ -459,8 +459,9 @@ class GPUCommon : public GPUDebugInterface { bool resumingFromDebugBreak_ = false; bool dumpNextFrame_ = false; bool dumpThisFrame_ = false; - bool debugRecording_; - bool interruptsEnabled_; + bool useFastRunLoop_ = false; + bool debugRecording_ = false; + bool interruptsEnabled_ = false; bool displayResized_ = false; bool renderResized_ = false; bool configChanged_ = false; diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index b92cb7c1beb4..2a8e4c72ed82 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -896,7 +896,7 @@ void TransformUnit::Flush(const char *reason) { return; binner_->Flush(reason); - GPUDebug::NotifyDraw(); + GPUDebug::NotifyFlush(); hasDraws_ = false; } diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index 3285ef13d561..47b466fc11ee 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -569,7 +569,7 @@ void DrawEngineVulkan::DoFlush() { framebufferManager_->SetColorUpdated(gstate_c.skipDrawReason); - GPUDebug::NotifyDraw(); + GPUDebug::NotifyFlush(); } void DrawEngineVulkan::ResetAfterDraw() { diff --git a/Windows/GEDebugger/GEDebugger.cpp b/Windows/GEDebugger/GEDebugger.cpp index 2878ad186734..4bcf4de7e416 100644 --- a/Windows/GEDebugger/GEDebugger.cpp +++ b/Windows/GEDebugger/GEDebugger.cpp @@ -925,8 +925,6 @@ BOOL CGEDebugger::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { return TRUE; case WM_CLOSE: - GPUDebug::SetActive(false); - stepCountDlg.Show(false); Show(false); return TRUE; @@ -978,7 +976,7 @@ BOOL CGEDebugger::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { break; case IDC_GEDBG_FBTABS: fbTabs->HandleNotify(lParam); - if (GPUDebug::IsActive() && gpuDebug != nullptr) { + if (gpuDebug != nullptr) { UpdatePreviews(); } break; @@ -1025,7 +1023,6 @@ BOOL CGEDebugger::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { case IDC_GEDBG_BREAKTEX: { - GPUDebug::SetActive(true); if (!gpuDebug) { break; } @@ -1044,7 +1041,6 @@ BOOL CGEDebugger::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { case IDC_GEDBG_BREAKTARGET: { - GPUDebug::SetActive(true); if (!gpuDebug) { break; } @@ -1062,15 +1058,15 @@ BOOL CGEDebugger::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { break; case IDC_GEDBG_TEXLEVELDOWN: - UpdateTextureLevel(textureLevel_ - 1); - if (GPUDebug::IsActive() && gpuDebug != nullptr) { + if (gpuDebug != nullptr) { + UpdateTextureLevel(textureLevel_ - 1); UpdatePreviews(); } break; case IDC_GEDBG_TEXLEVELUP: - UpdateTextureLevel(textureLevel_ + 1); - if (GPUDebug::IsActive() && gpuDebug != nullptr) { + if (gpuDebug != nullptr) { + UpdateTextureLevel(textureLevel_ + 1); UpdatePreviews(); } break; @@ -1094,7 +1090,7 @@ BOOL CGEDebugger::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { break; case IDC_GEDBG_FLUSH: - if (GPUDebug::IsActive() && gpuDebug != nullptr) { + if (gpuDebug != nullptr) { if (!autoFlush_) GPU_FlushDrawing(); UpdatePreviews(); @@ -1106,14 +1102,14 @@ BOOL CGEDebugger::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { break; case IDC_GEDBG_FORCEOPAQUE: - if (GPUDebug::IsActive() && gpuDebug != nullptr) { + if (gpuDebug != nullptr) { forceOpaque_ = SendMessage(GetDlgItem(m_hDlg, IDC_GEDBG_FORCEOPAQUE), BM_GETCHECK, 0, 0) != 0; UpdatePreviews(); } break; case IDC_GEDBG_SHOWCLUT: - if (GPUDebug::IsActive() && gpuDebug != nullptr) { + if (gpuDebug != nullptr) { showClut_ = SendMessage(GetDlgItem(m_hDlg, IDC_GEDBG_SHOWCLUT), BM_GETCHECK, 0, 0) != 0; UpdatePreviews(); } @@ -1136,7 +1132,6 @@ BOOL CGEDebugger::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { case WM_GEDBG_TOGGLEPCBREAKPOINT: { - GPUDebug::SetActive(true); u32 pc = (u32)wParam; bool temp; bool isBreak = IsAddressBreakpoint(pc, temp); @@ -1156,7 +1151,6 @@ BOOL CGEDebugger::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { case WM_GEDBG_RUNTOWPARAM: { - GPUDebug::SetActive(true); u32 pc = (u32)wParam; AddAddressBreakpoint(pc, true); SendMessage(m_hDlg,WM_COMMAND,IDC_GEDBG_RESUME,0); From 17e0680c1292a5a68411f78bbdbd06426069b98b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 15 Dec 2024 11:20:03 +0100 Subject: [PATCH 55/58] ImGeDebugger: Show the stall address (if any) in red. --- GPU/GPUCommon.cpp | 3 +-- UI/ImDebugger/ImGe.cpp | 11 +++++------ UI/ImDebugger/ImGe.h | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 2a3b5d7d4773..33c7d2d0d53c 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -740,8 +740,7 @@ int GPUCommon::GetNextListIndex() { } } -// This is now called when coreState == CORE_RUNNING_GE. -// TODO: It should return the next action.. (break into debugger or continue running) +// This is now called when coreState == CORE_RUNNING_GE, in addition to from the various sceGe commands. DLResult GPUCommon::ProcessDLQueue() { if (!resumingFromDebugBreak_) { startingTicks = CoreTiming::GetTicks(); diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index 33638556782c..30eb8869ed10 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -129,16 +129,15 @@ void ImGeDisasmView::Draw(GPUDebugInterface *gpuDebug) { stallAddr = displayList.stall; } - if (pc != 0xFFFFFFFF) { - if (gotoPC_) { - selectedAddr_ = pc; - gotoPC_ = false; - } + if (pc != 0xFFFFFFFF && gotoPC_) { + selectedAddr_ = pc; + gotoPC_ = false; } float pcY = canvas_p0.y + ((pc - windowStartAddr) / instrWidth) * lineHeight; draw_list->AddRectFilled(ImVec2(canvas_p0.x, pcY), ImVec2(canvas_p1.x, pcY + lineHeight), IM_COL32(0x10, 0x70, 0x10, 255)); - + float stallY = canvas_p0.y + ((stallAddr - windowStartAddr) / instrWidth) * lineHeight; + draw_list->AddRectFilled(ImVec2(canvas_p0.x, stallY), ImVec2(canvas_p1.x, stallY + lineHeight), IM_COL32(0x70, 0x20, 0x10, 255)); u32 addr = windowStartAddr; for (int line = 0; line < numLines; line++) { char addrBuffer[128]; diff --git a/UI/ImDebugger/ImGe.h b/UI/ImDebugger/ImGe.h index 895ba8964579..3f11702c37e9 100644 --- a/UI/ImDebugger/ImGe.h +++ b/UI/ImDebugger/ImGe.h @@ -35,7 +35,7 @@ class ImGeDisasmView { void NotifyStep(); private: - u32 selectedAddr_ = INVALID_ADDR; + u32 selectedAddr_ = 0; u32 dragAddr_ = INVALID_ADDR; bool bpPopup_ = false; bool gotoPC_ = false; From 638607d29ad59ad117879f02f3a2c8ea6dc11c2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 15 Dec 2024 12:07:44 +0100 Subject: [PATCH 56/58] Refactor: Make GPUCommon own the framedump "recorder". --- Core/Core.cpp | 2 +- .../WebSocket/GPURecordSubscriber.cpp | 6 +- Core/HLE/sceDisplay.cpp | 2 +- GPU/Common/FramebufferManagerCommon.cpp | 4 +- GPU/Common/FramebufferManagerCommon.h | 6 +- GPU/Common/GPUDebugInterface.h | 6 + GPU/Common/TextureCacheCommon.cpp | 4 +- GPU/Common/TextureCacheCommon.h | 6 +- GPU/Debugger/Playback.cpp | 2 +- GPU/Debugger/Record.cpp | 117 ++++++------------ GPU/Debugger/Record.h | 90 ++++++++++++-- GPU/Debugger/Stepping.cpp | 4 +- GPU/Debugger/Stepping.h | 2 +- GPU/GPUCommon.cpp | 16 +-- GPU/GPUCommon.h | 7 ++ GPU/GPUCommonHW.cpp | 4 +- GPU/Software/SoftGpu.cpp | 8 +- UI/DevScreens.cpp | 2 +- Windows/GEDebugger/GEDebugger.cpp | 2 +- 19 files changed, 171 insertions(+), 119 deletions(-) diff --git a/Core/Core.cpp b/Core/Core.cpp index d843047808dc..15d1c42d3b62 100644 --- a/Core/Core.cpp +++ b/Core/Core.cpp @@ -187,7 +187,7 @@ void Core_RunLoopUntil(u64 globalticks) { case CORE_RUNNING_GE: switch (gpu->ProcessDLQueue()) { case DLResult::DebugBreak: - GPUStepping::EnterStepping(); + GPUStepping::EnterStepping(coreState); break; case DLResult::Error: // We should elegantly report the error somehow, or I guess ignore it. diff --git a/Core/Debugger/WebSocket/GPURecordSubscriber.cpp b/Core/Debugger/WebSocket/GPURecordSubscriber.cpp index 30516ba81e83..d8a54d2e3060 100644 --- a/Core/Debugger/WebSocket/GPURecordSubscriber.cpp +++ b/Core/Debugger/WebSocket/GPURecordSubscriber.cpp @@ -21,6 +21,8 @@ #include "Core/Debugger/WebSocket/WebSocketUtils.h" #include "Core/System.h" #include "GPU/Debugger/Record.h" +#include "GPU/GPU.h" +#include "GPU/Common/GPUDebugInterface.h" struct WebSocketGPURecordState : public DebuggerSubscriber { ~WebSocketGPURecordState(); @@ -44,7 +46,7 @@ DebuggerSubscriber *WebSocketGPURecordInit(DebuggerEventHandlerMap &map) { WebSocketGPURecordState::~WebSocketGPURecordState() { // Clear the callback to hopefully avoid a crash. if (pending_) - GPURecord::ClearCallback(); + gpuDebug->GetRecorder()->ClearCallback(); } // Begin recording (gpu.record.dump) @@ -60,7 +62,7 @@ void WebSocketGPURecordState::Dump(DebuggerRequest &req) { return req.Fail("CPU not started"); } - bool result = GPURecord::RecordNextFrame([=](const Path &filename) { + bool result = gpuDebug->GetRecorder()->RecordNextFrame([=](const Path &filename) { lastFilename_ = filename; pending_ = false; }); diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index 609f17dd00b2..a0ba14ca133f 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -676,7 +676,7 @@ void __DisplayFlip(int cyclesLate) { // 4 here means 1 drawn, 4 skipped - so 12 fps minimum. maxFrameskip = frameSkipNum; } - if (numSkippedFrames >= maxFrameskip || GPURecord::IsActivePending()) { + if (numSkippedFrames >= maxFrameskip || gpuDebug->GetRecorder()->IsActivePending()) { skipFrame = false; } diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index cacc866f8f9a..84f034f9ee3b 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -140,14 +140,14 @@ bool FramebufferManagerCommon::PresentedThisFrame() const { return presentation_->PresentedThisFrame(); } -void FramebufferManagerCommon::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) { +void FramebufferManagerCommon::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format, GPURecord::Recorder *recorder) { displayFramebufPtr_ = framebuf & 0x3FFFFFFF; if (Memory::IsVRAMAddress(displayFramebufPtr_)) displayFramebufPtr_ = framebuf & 0x041FFFFF; displayStride_ = stride; displayFormat_ = format; GPUDebug::NotifyDisplay(framebuf, stride, format); - GPURecord::NotifyDisplay(framebuf, stride, format); + recorder->NotifyDisplay(framebuf, stride, format); } VirtualFramebuffer *FramebufferManagerCommon::GetVFBAt(u32 addr) const { diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index 37a71d751b24..cbd13773dd01 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -273,6 +273,10 @@ namespace Draw { class DrawContext; } +namespace GPURecord { +class Recorder; +} + struct DrawPixelsEntry { Draw::Texture *tex; uint64_t contentsHash; @@ -302,7 +306,7 @@ class FramebufferManagerCommon { void Init(int msaaLevel); virtual void BeginFrame(); - void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format); + void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format, GPURecord::Recorder *recorder); void DestroyFramebuf(VirtualFramebuffer *v); VirtualFramebuffer *DoSetRenderFrameBuffer(FramebufferHeuristicParams ¶ms, u32 skipDrawReason); diff --git a/GPU/Common/GPUDebugInterface.h b/GPU/Common/GPUDebugInterface.h index 35e607db75a6..c62c1afd6dbe 100644 --- a/GPU/Common/GPUDebugInterface.h +++ b/GPU/Common/GPUDebugInterface.h @@ -34,6 +34,10 @@ class TextureCacheCommon; struct VirtualFramebuffer; struct DisplayList; +namespace GPURecord { +class Recorder; +} + struct GPUDebugOp { u32 pc; u8 cmd; @@ -239,6 +243,8 @@ class GPUDebugInterface { virtual const std::list &GetDisplayListQueue() = 0; virtual const DisplayList &GetDisplayList(int index) = 0; + virtual GPURecord::Recorder *GetRecorder() = 0; + virtual bool GetCurrentSimpleVertices(int count, std::vector &vertices, std::vector &indices) { return false; } diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 84cbf9067954..5a8f3bd12d91 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -1310,7 +1310,7 @@ void TextureCacheCommon::NotifyWriteFormattedFromMemory(u32 addr, int size, int videos_.push_back({ addr, (u32)size, gpuStats.numFlips }); } -void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) { +void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes, GPURecord::Recorder *recorder) { if (loadBytes == 0) { // Don't accidentally overwrite clutTotalBytes_ with a zero. return; @@ -1430,7 +1430,7 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) { u32 bytes = Memory::ValidSize(clutAddr, loadBytes); _assert_(bytes <= 2048); bool performDownload = PSP_CoreParameter().compat.flags().AllowDownloadCLUT; - if (GPURecord::IsActive()) + if (recorder->IsActive()) performDownload = true; if (clutRenderAddress_ != 0xFFFFFFFF && performDownload) { framebufferManager_->DownloadFramebufferForClut(clutRenderAddress_, clutRenderOffset_ + bytes); diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index 48b29dd35990..48fc25dd54ef 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -63,6 +63,10 @@ class DrawContext; class Texture; } +namespace GPURecord { +class Recorder; +} + // Used by D3D11 and Vulkan, could be used by modern GL struct SamplerCacheKey { union { @@ -334,7 +338,7 @@ class TextureCacheCommon { TextureCacheCommon(Draw::DrawContext *draw, Draw2D *draw2D); virtual ~TextureCacheCommon(); - void LoadClut(u32 clutAddr, u32 loadBytes); + void LoadClut(u32 clutAddr, u32 loadBytes, GPURecord::Recorder *recorder); bool GetCurrentClutBuffer(GPUDebugBuffer &buffer); // This updates nextTexture_ / nextFramebufferTexture_, which is then used by ApplyTexture. diff --git a/GPU/Debugger/Playback.cpp b/GPU/Debugger/Playback.cpp index 1a2b951bb1ab..e9122e796d3b 100644 --- a/GPU/Debugger/Playback.cpp +++ b/GPU/Debugger/Playback.cpp @@ -909,7 +909,7 @@ void WriteRunDumpCode(u32 codeStart) { // This is called by the syscall. ReplayResult RunMountedReplay(const std::string &filename) { - _assert_msg_(!GPURecord::IsActivePending(), "Cannot run replay while recording."); + _assert_msg_(!gpuDebug->GetRecorder()->IsActivePending(), "Cannot run replay while recording."); Core_ListenStopRequest(&ReplayStop); diff --git a/GPU/Debugger/Record.cpp b/GPU/Debugger/Record.cpp index f08b2067aa03..67d03452c687 100644 --- a/GPU/Debugger/Record.cpp +++ b/GPU/Debugger/Record.cpp @@ -48,35 +48,9 @@ namespace GPURecord { -static bool active = false; -static std::atomic nextFrame = false; -static int flipLastAction = -1; -static int flipFinishAt = -1; -static uint32_t lastEdramTrans = 0x400; -static std::function writeCallback; - -static std::vector pushbuf; -static std::vector commands; -static std::vector lastRegisters; -static std::vector lastTextures; -static std::set lastRenderTargets; -static std::vector lastVRAM; - -enum class DirtyVRAMFlag : uint8_t { - CLEAN = 0, - UNKNOWN = 1, - DIRTY = 2, - DRAWN = 3, -}; -static constexpr uint32_t DIRTY_VRAM_SHIFT = 8; -static constexpr uint32_t DIRTY_VRAM_ROUND = (1 << DIRTY_VRAM_SHIFT) - 1; -static constexpr uint32_t DIRTY_VRAM_SIZE = (2 * 1024 * 1024) >> DIRTY_VRAM_SHIFT; -static constexpr uint32_t DIRTY_VRAM_MASK = (2 * 1024 * 1024 - 1) >> DIRTY_VRAM_SHIFT; -static DirtyVRAMFlag dirtyVRAM[DIRTY_VRAM_SIZE]; - -static void FlushRegisters() { +void Recorder::FlushRegisters() { if (!lastRegisters.empty()) { - Command last{CommandType::REGISTERS}; + Command last{ CommandType::REGISTERS }; last.ptr = (u32)pushbuf.size(); last.sz = (u32)(lastRegisters.size() * sizeof(u32)); pushbuf.resize(pushbuf.size() + last.sz); @@ -107,7 +81,7 @@ static Path GenRecordingFilename() { return dumpDir / StringFromFormat("%s_%04d.ppdmp", prefix.c_str(), 9999); } -static void DirtyAllVRAM(DirtyVRAMFlag flag) { +void Recorder::DirtyAllVRAM(DirtyVRAMFlag flag) { if (flag == DirtyVRAMFlag::UNKNOWN) { for (uint32_t i = 0; i < DIRTY_VRAM_SIZE; ++i) { if (dirtyVRAM[i] == DirtyVRAMFlag::CLEAN) @@ -119,7 +93,7 @@ static void DirtyAllVRAM(DirtyVRAMFlag flag) { } } -static void DirtyVRAM(u32 start, u32 sz, DirtyVRAMFlag flag) { +void Recorder::DirtyVRAM(u32 start, u32 sz, DirtyVRAMFlag flag) { u32 count = (sz + DIRTY_VRAM_ROUND) >> DIRTY_VRAM_SHIFT; u32 first = (start >> DIRTY_VRAM_SHIFT) & DIRTY_VRAM_MASK; if (first + count > DIRTY_VRAM_SIZE) { @@ -131,7 +105,7 @@ static void DirtyVRAM(u32 start, u32 sz, DirtyVRAMFlag flag) { dirtyVRAM[first + i] = flag; } -static void DirtyDrawnVRAM() { +void Recorder::DirtyDrawnVRAM() { int w = std::min(gstate.getScissorX2(), gstate.getRegionX2()) + 1; int h = std::min(gstate.getScissorY2(), gstate.getRegionY2()) + 1; @@ -151,7 +125,7 @@ static void DirtyDrawnVRAM() { DirtyVRAM(gstate.getFrameBufAddress(), bytes, DirtyVRAMFlag::DRAWN); } -static bool BeginRecording() { +bool Recorder::BeginRecording() { if (PSP_CoreParameter().fileType == IdentifiedFileType::PPSSPP_GE_DUMP) { // Can't record a GE dump. return false; @@ -168,7 +142,7 @@ static bool BeginRecording() { u32 sz = 512 * 4; pushbuf.resize(pushbuf.size() + sz); gstate.Save((u32_le *)(pushbuf.data() + ptr)); - commands.push_back({CommandType::INIT, sz, ptr}); + commands.push_back({ CommandType::INIT, sz, ptr }); lastVRAM.resize(2 * 1024 * 1024); // Also save the initial CLUT. @@ -195,10 +169,10 @@ static void WriteCompressed(FILE *fp, const void *p, size_t sz) { fwrite(&write_size, sizeof(write_size), 1, fp); fwrite(compressed, compressed_size, 1, fp); - delete [] compressed; + delete[] compressed; } -static Path WriteRecording() { +Path Recorder::WriteRecording() { FlushRegisters(); const Path filename = GenRecordingFilename(); @@ -298,10 +272,10 @@ static const u8 *mymemmem(const u8 *haystack, size_t off, size_t hlen, const u8 return result; } -static Command EmitCommandWithRAM(CommandType t, const void *p, u32 sz, u32 align) { +Command Recorder::EmitCommandWithRAM(CommandType t, const void *p, u32 sz, u32 align) { FlushRegisters(); - Command cmd{t, sz, 0}; + Command cmd{ t, sz, 0 }; if (sz) { // If at all possible, try to find it already in the buffer. @@ -337,7 +311,7 @@ static Command EmitCommandWithRAM(CommandType t, const void *p, u32 sz, u32 alig return cmd; } -static void UpdateLastVRAM(u32 addr, u32 bytes) { +void Recorder::UpdateLastVRAM(u32 addr, u32 bytes) { u32 base = addr & 0x001FFFFF; if (base + bytes > 0x00200000) { memcpy(&lastVRAM[base], Memory::GetPointerUnchecked(0x04000000 | base), 0x00200000 - base); @@ -347,7 +321,7 @@ static void UpdateLastVRAM(u32 addr, u32 bytes) { memcpy(&lastVRAM[base], Memory::GetPointerUnchecked(0x04000000 | base), bytes); } -static void ClearLastVRAM(u32 addr, u8 c, u32 bytes) { +void Recorder::ClearLastVRAM(u32 addr, u8 c, u32 bytes) { u32 base = addr & 0x001FFFFF; if (base + bytes > 0x00200000) { memset(&lastVRAM[base], c, 0x00200000 - base); @@ -357,7 +331,7 @@ static void ClearLastVRAM(u32 addr, u8 c, u32 bytes) { memset(&lastVRAM[base], c, bytes); } -static int CompareLastVRAM(u32 addr, u32 bytes) { +int Recorder::CompareLastVRAM(u32 addr, u32 bytes) const { u32 base = addr & 0x001FFFFF; if (base + bytes > 0x00200000) { int result = memcmp(&lastVRAM[base], Memory::GetPointerUnchecked(0x04000000 | base), 0x00200000 - base); @@ -370,7 +344,7 @@ static int CompareLastVRAM(u32 addr, u32 bytes) { return memcmp(&lastVRAM[base], Memory::GetPointerUnchecked(0x04000000 | base), bytes); } -static u32 GetTargetFlags(u32 addr, u32 sizeInRAM) { +u32 Recorder::GetTargetFlags(u32 addr, u32 sizeInRAM) { addr &= 0x041FFFFF; const bool isTarget = lastRenderTargets.find(addr) != lastRenderTargets.end(); @@ -416,7 +390,7 @@ static u32 GetTargetFlags(u32 addr, u32 sizeInRAM) { return flags; } -static void EmitTextureData(int level, u32 texaddr) { +void Recorder::EmitTextureData(int level, u32 texaddr) { GETextureFormat format = gstate.getTextureFormat(); int w = gstate.getTextureWidth(level); int h = gstate.getTextureHeight(level); @@ -462,7 +436,7 @@ static void EmitTextureData(int level, u32 texaddr) { } if (memcmp(pushbuf.data() + prevptr, p, bytes) == 0) { - commands.push_back({type, bytes, prevptr}); + commands.push_back({ type, bytes, prevptr }); // Okay, that was easy. Bail out. return; } @@ -474,7 +448,7 @@ static void EmitTextureData(int level, u32 texaddr) { } } -static void FlushPrimState(int vcount) { +void Recorder::FlushPrimState(int vcount) { // TODO: Eventually, how do we handle texturing from framebuf/zbuf? // TODO: Do we need to preload color/depth/stencil (in case from last frame)? @@ -510,7 +484,7 @@ static void FlushPrimState(int vcount) { } } -static void EmitTransfer(u32 op) { +void Recorder::EmitTransfer(u32 op) { FlushRegisters(); // This may not make a lot of sense right now, unless it's to a framebuf... @@ -545,7 +519,7 @@ static void EmitTransfer(u32 op) { lastRegisters.push_back(op); } -static void EmitClut(u32 op) { +void Recorder::EmitClut(u32 op) { u32 addr = gstate.getClutAddress(); // Hardware rendering may be using a framebuffer as CLUT. @@ -569,7 +543,7 @@ static void EmitClut(u32 op) { ClutAddrData data{ addr, flags }; FlushRegisters(); - Command cmd{CommandType::CLUTADDR, sizeof(data), (u32)pushbuf.size()}; + Command cmd{ CommandType::CLUTADDR, sizeof(data), (u32)pushbuf.size() }; pushbuf.resize(pushbuf.size() + sizeof(data)); memcpy(pushbuf.data() + cmd.ptr, &data, sizeof(data)); commands.push_back(cmd); @@ -583,14 +557,14 @@ static void EmitClut(u32 op) { lastRegisters.push_back(op); } -static void EmitPrim(u32 op) { +void Recorder::EmitPrim(u32 op) { FlushPrimState(op & 0x0000FFFF); lastRegisters.push_back(op); DirtyDrawnVRAM(); } -static void EmitBezierSpline(u32 op) { +void Recorder::EmitBezierSpline(u32 op) { int ucount = op & 0xFF; int vcount = (op >> 8) & 0xFF; FlushPrimState(ucount * vcount); @@ -599,15 +573,7 @@ static void EmitBezierSpline(u32 op) { DirtyDrawnVRAM(); } -bool IsActive() { - return active; -} - -bool IsActivePending() { - return nextFrame || active; -} - -bool RecordNextFrame(const std::function callback) { +bool Recorder::RecordNextFrame(const std::function callback) { if (!nextFrame) { flipLastAction = gpuStats.numFlips; flipFinishAt = -1; @@ -618,12 +584,7 @@ bool RecordNextFrame(const std::function callback) { return false; } -void ClearCallback() { - // Not super thread safe.. - writeCallback = nullptr; -} - -static void FinishRecording() { +void Recorder::FinishRecording() { // We're done - this was just to write the result out. if (!active) { return; @@ -646,7 +607,7 @@ static void FinishRecording() { writeCallback = nullptr; } -static void CheckEdramTrans() { +void Recorder::CheckEdramTrans() { if (!gpuDebug) return; @@ -656,13 +617,13 @@ static void CheckEdramTrans() { lastEdramTrans = value; FlushRegisters(); - Command cmd{CommandType::EDRAMTRANS, sizeof(value), (u32)pushbuf.size()}; + Command cmd{ CommandType::EDRAMTRANS, sizeof(value), (u32)pushbuf.size() }; pushbuf.resize(pushbuf.size() + sizeof(value)); memcpy(pushbuf.data() + cmd.ptr, &value, sizeof(value)); commands.push_back(cmd); } -void NotifyCommand(u32 pc) { +void Recorder::NotifyCommand(u32 pc) { if (!active) { return; } @@ -716,7 +677,7 @@ void NotifyCommand(u32 pc) { } } -void NotifyMemcpy(u32 dest, u32 src, u32 sz) { +void Recorder::NotifyMemcpy(u32 dest, u32 src, u32 sz) { if (!active) { return; } @@ -724,7 +685,7 @@ void NotifyMemcpy(u32 dest, u32 src, u32 sz) { CheckEdramTrans(); if (Memory::IsVRAMAddress(dest)) { FlushRegisters(); - Command cmd{CommandType::MEMCPYDEST, sizeof(dest), (u32)pushbuf.size()}; + Command cmd{ CommandType::MEMCPYDEST, sizeof(dest), (u32)pushbuf.size() }; pushbuf.resize(pushbuf.size() + sizeof(dest)); memcpy(pushbuf.data() + cmd.ptr, &dest, sizeof(dest)); commands.push_back(cmd); @@ -738,7 +699,7 @@ void NotifyMemcpy(u32 dest, u32 src, u32 sz) { } } -void NotifyMemset(u32 dest, int v, u32 sz) { +void Recorder::NotifyMemset(u32 dest, int v, u32 sz) { if (!active) { return; } @@ -752,10 +713,10 @@ void NotifyMemset(u32 dest, int v, u32 sz) { if (Memory::IsVRAMAddress(dest)) { sz = Memory::ValidSize(dest, sz); - MemsetCommand data{dest, v, sz}; + MemsetCommand data{ dest, v, sz }; FlushRegisters(); - Command cmd{CommandType::MEMSET, sizeof(data), (u32)pushbuf.size()}; + Command cmd{ CommandType::MEMSET, sizeof(data), (u32)pushbuf.size() }; pushbuf.resize(pushbuf.size() + sizeof(data)); memcpy(pushbuf.data() + cmd.ptr, &data, sizeof(data)); commands.push_back(cmd); @@ -764,12 +725,12 @@ void NotifyMemset(u32 dest, int v, u32 sz) { } } -void NotifyUpload(u32 dest, u32 sz) { +void Recorder::NotifyUpload(u32 dest, u32 sz) { // This also checks the edram translation value and dirties VRAM. NotifyMemcpy(dest, dest, sz); } -static bool HasDrawCommands() { +bool Recorder::HasDrawCommands() const { if (commands.empty()) return false; @@ -788,7 +749,7 @@ static bool HasDrawCommands() { return false; } -void NotifyDisplay(u32 framebuf, int stride, int fmt) { +void Recorder::NotifyDisplay(u32 framebuf, int stride, int fmt) { bool writePending = false; if (active && HasDrawCommands()) { writePending = true; @@ -823,7 +784,7 @@ void NotifyDisplay(u32 framebuf, int stride, int fmt) { } } -void NotifyBeginFrame() { +void Recorder::NotifyBeginFrame() { const bool noDisplayAction = flipLastAction + 4 < gpuStats.numFlips; // We do this only to catch things that don't call NotifyDisplay. if (active && HasDrawCommands() && (noDisplayAction || gpuStats.numFlips == flipFinishAt)) { @@ -856,7 +817,7 @@ void NotifyBeginFrame() { } } -void NotifyCPU() { +void Recorder::NotifyCPU() { if (!active) { return; } @@ -864,4 +825,4 @@ void NotifyCPU() { DirtyAllVRAM(DirtyVRAMFlag::UNKNOWN); } -}; +} // namespace GPURecord diff --git a/GPU/Debugger/Record.h b/GPU/Debugger/Record.h index 454fc4926c43..514ca1c04c52 100644 --- a/GPU/Debugger/Record.h +++ b/GPU/Debugger/Record.h @@ -18,24 +18,92 @@ #pragma once #include +#include +#include +#include #include "Common/CommonTypes.h" +#include "GPU/Debugger/RecordFormat.h" class Path; namespace GPURecord { -bool IsActive(); -bool IsActivePending(); -bool RecordNextFrame(const std::function callback); -void ClearCallback(); +constexpr uint32_t DIRTY_VRAM_SHIFT = 8; +constexpr uint32_t DIRTY_VRAM_ROUND = (1 << DIRTY_VRAM_SHIFT) - 1; +constexpr uint32_t DIRTY_VRAM_SIZE = (2 * 1024 * 1024) >> DIRTY_VRAM_SHIFT; +constexpr uint32_t DIRTY_VRAM_MASK = (2 * 1024 * 1024 - 1) >> DIRTY_VRAM_SHIFT; +enum class DirtyVRAMFlag : uint8_t { + CLEAN = 0, + UNKNOWN = 1, + DIRTY = 2, + DRAWN = 3, +}; + +class Recorder { +public: + bool IsActive() const { + return active; + } + bool IsActivePending() const { + return nextFrame || active; + } + bool RecordNextFrame(const std::function callback); + void ClearCallback() { + // Not super thread safe.. + writeCallback = nullptr; + } + + void NotifyCommand(u32 pc); + void NotifyMemcpy(u32 dest, u32 src, u32 sz); + void NotifyMemset(u32 dest, int v, u32 sz); + void NotifyUpload(u32 dest, u32 sz); + void NotifyDisplay(u32 addr, int stride, int fmt); + void NotifyBeginFrame(); + void NotifyCPU(); +private: + void FlushRegisters(); + void DirtyAllVRAM(DirtyVRAMFlag flag); + void DirtyVRAM(u32 start, u32 sz, DirtyVRAMFlag flag); + void DirtyDrawnVRAM(); + + bool BeginRecording(); + Path WriteRecording(); + + bool HasDrawCommands() const; + void CheckEdramTrans(); + void FinishRecording(); + + Command EmitCommandWithRAM(CommandType t, const void *p, u32 sz, u32 align); -void NotifyCommand(u32 pc); -void NotifyMemcpy(u32 dest, u32 src, u32 sz); -void NotifyMemset(u32 dest, int v, u32 sz); -void NotifyUpload(u32 dest, u32 sz); -void NotifyDisplay(u32 addr, int stride, int fmt); -void NotifyBeginFrame(); -void NotifyCPU(); + void UpdateLastVRAM(u32 addr, u32 bytes); + void ClearLastVRAM(u32 addr, u8 c, u32 bytes); + int CompareLastVRAM(u32 addr, u32 bytes) const; + u32 GetTargetFlags(u32 addr, u32 sizeInRAM); + + void FlushPrimState(int vcount); + void EmitTextureData(int level, u32 texaddr); + void EmitTransfer(u32 op); + void EmitClut(u32 op); + void EmitPrim(u32 op); + void EmitBezierSpline(u32 op); + + bool active = false; + std::atomic nextFrame = false; + int flipLastAction = -1; + int flipFinishAt = -1; + uint32_t lastEdramTrans = 0x400; + std::function writeCallback; + + std::vector pushbuf; + std::vector commands; + std::vector lastRegisters; + std::vector lastTextures; + std::set lastRenderTargets; + std::vector lastVRAM; + + DirtyVRAMFlag dirtyVRAM[DIRTY_VRAM_SIZE]; }; + +} // namespace GPURecord diff --git a/GPU/Debugger/Stepping.cpp b/GPU/Debugger/Stepping.cpp index 4395a3275a5e..ff15a566a647 100644 --- a/GPU/Debugger/Stepping.cpp +++ b/GPU/Debugger/Stepping.cpp @@ -186,7 +186,7 @@ bool ProcessStepping() { return true; } -bool EnterStepping() { +bool EnterStepping(CoreState coreState) { _dbg_assert_(gpuDebug); std::unique_lock guard(pauseLock); @@ -216,7 +216,7 @@ bool EnterStepping() { pauseAction = PAUSE_BREAK; } - coreState = CORE_STEPPING_GE; + ::coreState = CORE_STEPPING_GE; return true; } diff --git a/GPU/Debugger/Stepping.h b/GPU/Debugger/Stepping.h index 0146053a8aa8..9c01f0836339 100644 --- a/GPU/Debugger/Stepping.h +++ b/GPU/Debugger/Stepping.h @@ -27,7 +27,7 @@ namespace GPUStepping { // Should be called from the emu thread. // Begins stepping and increments the stepping counter while inside a lock. - bool EnterStepping(); + bool EnterStepping(CoreState coreState); bool IsStepping(); void ResumeFromStepping(); diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 33c7d2d0d53c..6ff2d895c843 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -625,7 +625,7 @@ void GPUCommon::PSPFrame() { dumpThisFrame_ = false; } GPUDebug::NotifyBeginFrame(); - GPURecord::NotifyBeginFrame(); + recorder_.NotifyBeginFrame(); } // Returns false on breakpoint. @@ -635,7 +635,7 @@ bool GPUCommon::SlowRunLoop(DisplayList &list) { GPUDebug::NotifyResult result = GPUDebug::NotifyCommand(list.pc); if (result == GPUDebug::NotifyResult::Execute) { - GPURecord::NotifyCommand(list.pc); + recorder_.NotifyCommand(list.pc); u32 op = Memory::ReadUnchecked_U32(list.pc); u32 cmd = op >> 24; @@ -790,7 +790,7 @@ DLResult GPUCommon::ProcessDLQueue() { gpuState = list.pc == list.stall ? GPUSTATE_STALL : GPUSTATE_RUNNING; // To enable breakpoints, we don't do fast matrix loads while debugger active. - debugRecording_ = GPURecord::IsActive(); + debugRecording_ = recorder_.IsActive(); useFastRunLoop_ = !(dumpThisFrame_ || debugRecording_ || GPUDebug::NeedsSlowInterpreter()); } else { resumingFromDebugBreak_ = false; @@ -821,7 +821,7 @@ DLResult GPUCommon::ProcessDLQueue() { // Hit a breakpoint, so we set the state and bail. We can resume later. // TODO: Cycle counting might need some more care? FinishDeferred(); - _dbg_assert_(!GPURecord::IsActive()); + _dbg_assert_(!recorder_.IsActive()); resumingFromDebugBreak_ = true; return DLResult::DebugBreak; @@ -837,7 +837,7 @@ DLResult GPUCommon::ProcessDLQueue() { FinishDeferred(); if (debugRecording_) - GPURecord::NotifyCPU(); + recorder_.NotifyCPU(); // We haven't run the op at list.pc, so it shouldn't count. if (cycleLastPC != list.pc) { @@ -1944,7 +1944,7 @@ bool GPUCommon::PerformMemoryCopy(u32 dest, u32 src, int size, GPUCopyFlag flags } InvalidateCache(dest, size, GPU_INVALIDATE_HINT); if (!(flags & GPUCopyFlag::DEBUG_NOTIFIED)) - GPURecord::NotifyMemcpy(dest, src, size); + recorder_.NotifyMemcpy(dest, src, size); return false; } @@ -1961,7 +1961,7 @@ bool GPUCommon::PerformMemorySet(u32 dest, u8 v, int size) { NotifyMemInfo(MemBlockFlags::WRITE, dest, size, "GPUMemset"); // Or perhaps a texture, let's invalidate. InvalidateCache(dest, size, GPU_INVALIDATE_HINT); - GPURecord::NotifyMemset(dest, v, size); + recorder_.NotifyMemset(dest, v, size); return false; } @@ -1974,7 +1974,7 @@ bool GPUCommon::PerformReadbackToMemory(u32 dest, int size) { bool GPUCommon::PerformWriteColorFromMemory(u32 dest, int size) { if (Memory::IsVRAMAddress(dest)) { - GPURecord::NotifyUpload(dest, size); + recorder_.NotifyUpload(dest, size); return PerformMemoryCopy(dest, dest, size, GPUCopyFlag::FORCE_SRC_MATCH_MEM | GPUCopyFlag::DEBUG_NOTIFIED); } return false; diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 4bdfcaf7a0ad..999b55d43a09 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -12,6 +12,7 @@ #include "GPU/GPU.h" #include "GPU/GPUCommon.h" #include "GPU/GPUState.h" +#include "GPU/Debugger/Record.h" #include "GPU/Common/ShaderCommon.h" #include "GPU/Common/GPUDebugInterface.h" #include "GPU/GPUDefinitions.h" @@ -377,6 +378,10 @@ class GPUCommon : public GPUDebugInterface { void PSPFrame(); + GPURecord::Recorder *GetRecorder() override { + return &recorder_; + } + protected: virtual void ClearCacheNextFrame() {} @@ -501,6 +506,8 @@ class GPUCommon : public GPUDebugInterface { std::string reportingPrimaryInfo_; std::string reportingFullInfo_; + GPURecord::Recorder recorder_; + private: void DoExecuteCall(u32 target); void PopDLQueue(); diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 4616b34b8a33..951c78c373f0 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -512,7 +512,7 @@ void GPUCommonHW::BeginHostFrame() { } void GPUCommonHW::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) { - framebufferManager_->SetDisplayFramebuffer(framebuf, stride, format); + framebufferManager_->SetDisplayFramebuffer(framebuf, stride, format, &recorder_); } void GPUCommonHW::CheckFlushOp(int cmd, u32 diff) { @@ -1457,7 +1457,7 @@ void GPUCommonHW::Execute_TexLevel(u32 op, u32 diff) { void GPUCommonHW::Execute_LoadClut(u32 op, u32 diff) { gstate_c.Dirty(DIRTY_TEXTURE_PARAMS); - textureCache_->LoadClut(gstate.getClutAddress(), gstate.getClutLoadBytes()); + textureCache_->LoadClut(gstate.getClutAddress(), gstate.getClutLoadBytes(), &recorder_); } diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index d0b0b8d035dc..e58d9adc12c7 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -494,7 +494,7 @@ void SoftGPU::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat for displayStride_ = stride; displayFormat_ = format; GPUDebug::NotifyDisplay(framebuf, stride, format); - GPURecord::NotifyDisplay(framebuf, stride, format); + recorder_.NotifyDisplay(framebuf, stride, format); } DSStretch g_DarkStalkerStretch; @@ -1305,7 +1305,7 @@ bool SoftGPU::PerformMemoryCopy(u32 dest, u32 src, int size, GPUCopyFlag flags) // Nothing to update. InvalidateCache(dest, size, GPU_INVALIDATE_HINT); if (!(flags & GPUCopyFlag::DEBUG_NOTIFIED)) - GPURecord::NotifyMemcpy(dest, src, size); + recorder_.NotifyMemcpy(dest, src, size); // Let's just be safe. MarkDirty(dest, size, SoftGPUVRAMDirty::DIRTY | SoftGPUVRAMDirty::REALLY_DIRTY); return false; @@ -1315,7 +1315,7 @@ bool SoftGPU::PerformMemorySet(u32 dest, u8 v, int size) { // Nothing to update. InvalidateCache(dest, size, GPU_INVALIDATE_HINT); - GPURecord::NotifyMemset(dest, v, size); + recorder_.NotifyMemset(dest, v, size); // Let's just be safe. MarkDirty(dest, size, SoftGPUVRAMDirty::DIRTY | SoftGPUVRAMDirty::REALLY_DIRTY); return false; @@ -1332,7 +1332,7 @@ bool SoftGPU::PerformWriteColorFromMemory(u32 dest, int size) { // Nothing to update. InvalidateCache(dest, size, GPU_INVALIDATE_HINT); - GPURecord::NotifyUpload(dest, size); + recorder_.NotifyUpload(dest, size); return false; } diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index eeb7df2f1485..fd0ea6e1855e 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -153,7 +153,7 @@ void DevMenuScreen::CreatePopupContents(UI::ViewGroup *parent) { }); items->Add(new Choice(dev->T("Create frame dump")))->OnClick.Add([](UI::EventParams &e) { - GPURecord::RecordNextFrame([](const Path &dumpPath) { + gpuDebug->GetRecorder()->RecordNextFrame([](const Path &dumpPath) { NOTICE_LOG(Log::System, "Frame dump created at '%s'", dumpPath.c_str()); if (System_GetPropertyBool(SYSPROP_CAN_SHOW_FILE)) { System_ShowFileInFolder(dumpPath); diff --git a/Windows/GEDebugger/GEDebugger.cpp b/Windows/GEDebugger/GEDebugger.cpp index 4bcf4de7e416..4b86803cec40 100644 --- a/Windows/GEDebugger/GEDebugger.cpp +++ b/Windows/GEDebugger/GEDebugger.cpp @@ -1083,7 +1083,7 @@ BOOL CGEDebugger::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { break; case IDC_GEDBG_RECORD: - GPURecord::RecordNextFrame([](const Path &path) { + gpuDebug->GetRecorder()->RecordNextFrame([](const Path &path) { // Opens a Windows Explorer window with the file, when done. System_ShowFileInFolder(path); }); From 4223bcfae1a27cda9d7e9cba0031ab8008548fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 15 Dec 2024 12:51:37 +0100 Subject: [PATCH 57/58] Move the ownership of GPU breakpoints to GPUCommon --- GPU/Common/GPUDebugInterface.h | 2 + GPU/Debugger/Breakpoints.cpp | 161 ++++++++++----------- GPU/Debugger/Breakpoints.h | 59 +++++++- GPU/Debugger/Debugger.cpp | 28 ++-- GPU/Debugger/Debugger.h | 4 +- GPU/Debugger/GECommandTable.cpp | 20 --- GPU/Debugger/GECommandTable.h | 2 - GPU/Debugger/State.h | 3 - GPU/GPUCommon.cpp | 6 +- GPU/GPUCommon.h | 5 + UI/ImDebugger/ImGe.cpp | 31 ++-- Windows/GEDebugger/CtrlDisplayListView.cpp | 8 +- Windows/GEDebugger/GEDebugger.cpp | 53 ++++--- Windows/GEDebugger/TabState.cpp | 21 ++- Windows/GEDebugger/TabVertices.cpp | 14 +- 15 files changed, 219 insertions(+), 198 deletions(-) diff --git a/GPU/Common/GPUDebugInterface.h b/GPU/Common/GPUDebugInterface.h index c62c1afd6dbe..079798d5ca4c 100644 --- a/GPU/Common/GPUDebugInterface.h +++ b/GPU/Common/GPUDebugInterface.h @@ -37,6 +37,7 @@ struct DisplayList; namespace GPURecord { class Recorder; } +class GPUBreakpoints; struct GPUDebugOp { u32 pc; @@ -244,6 +245,7 @@ class GPUDebugInterface { virtual const DisplayList &GetDisplayList(int index) = 0; virtual GPURecord::Recorder *GetRecorder() = 0; + virtual GPUBreakpoints *GetBreakpoints() = 0; virtual bool GetCurrentSimpleVertices(int count, std::vector &vertices, std::vector &indices) { return false; diff --git a/GPU/Debugger/Breakpoints.cpp b/GPU/Debugger/Breakpoints.cpp index debc260bba27..5dc20f3f362b 100644 --- a/GPU/Debugger/Breakpoints.cpp +++ b/GPU/Debugger/Breakpoints.cpp @@ -22,41 +22,10 @@ #include #include "Common/CommonFuncs.h" -#include "Common/Math/expression_parser.h" -#include "GPU/Common/GPUDebugInterface.h" #include "GPU/Debugger/Breakpoints.h" +#include "GPU/Debugger/GECommandTable.h" #include "GPU/GPUState.h" -namespace GPUBreakpoints { - -struct BreakpointInfo { - bool isConditional = false; - PostfixExpression expression; - std::string expressionString; -}; - -static std::mutex breaksLock; -static bool breakCmds[256]; -static BreakpointInfo breakCmdsInfo[256]; -static std::unordered_map breakPCs; -static std::set breakTextures; -static std::set breakRenderTargets; -// Small optimization to avoid a lock/lookup for the common case. -static size_t breakPCsCount = 0; -static size_t breakTexturesCount = 0; -static size_t breakRenderTargetsCount = 0; - -// If these are set, the above are also, but they should be temporary. -static bool breakCmdsTemp[256]; -static std::set breakPCsTemp; -static std::set breakTexturesTemp; -static std::set breakRenderTargetsTemp; -static bool textureChangeTemp = false; - -bool g_hasBreakpoints; - -static u32 lastTexture = 0xFFFFFFFF; - // These are commands we run before breaking on a texture. // They are commands that affect the decoding of the texture. const static u8 textureRelatedCmds[] = { @@ -72,9 +41,8 @@ const static u8 textureRelatedCmds[] = { // Sometimes found between clut/texture params. GE_CMD_TEXFLUSH, GE_CMD_TEXSYNC, }; -static std::vector nonTextureCmds; -void Init() { +void GPUBreakpoints::Init() { ClearAllBreakpoints(); nonTextureCmds.clear(); @@ -84,7 +52,7 @@ void Init() { } } -void AddNonTextureTempBreakpoints() { +void GPUBreakpoints::AddNonTextureTempBreakpoints() { for (int i = 0; i < 256; ++i) { if (nonTextureCmds[i]) { AddCmdBreakpoint(i, true); @@ -92,7 +60,7 @@ void AddNonTextureTempBreakpoints() { } } -u32 GetAdjustedTextureAddress(u32 op) { +static u32 GetAdjustedTextureAddress(u32 op) { const u8 cmd = op >> 24; bool interesting = (cmd >= GE_CMD_TEXADDR0 && cmd <= GE_CMD_TEXADDR7); interesting = interesting || (cmd >= GE_CMD_TEXBUFWIDTH0 && cmd <= GE_CMD_TEXBUFWIDTH7); @@ -114,7 +82,7 @@ u32 GetAdjustedTextureAddress(u32 op) { return addr; } -u32 GetAdjustedRenderTargetAddress(u32 op) { +static u32 GetAdjustedRenderTargetAddress(u32 op) { const u8 cmd = op >> 24; switch (cmd) { case GE_CMD_FRAMEBUFPTR: @@ -126,7 +94,7 @@ u32 GetAdjustedRenderTargetAddress(u32 op) { } // Note: this now always returns false, but still needs to be called. -void CheckForTextureChange(u32 op, u32 addr) { +void GPUBreakpoints::CheckForTextureChange(u32 op, u32 addr) { if (!textureChangeTemp) { return; } @@ -156,7 +124,7 @@ void CheckForTextureChange(u32 op, u32 addr) { } } -bool IsTextureCmdBreakpoint(u32 op) { +bool GPUBreakpoints::IsTextureCmdBreakpoint(u32 op) { const u32 addr = GetAdjustedTextureAddress(op); if (addr != (u32)-1) { CheckForTextureChange(op, addr); @@ -167,7 +135,7 @@ bool IsTextureCmdBreakpoint(u32 op) { } } -bool IsRenderTargetCmdBreakpoint(u32 op) { +bool GPUBreakpoints::IsRenderTargetCmdBreakpoint(u32 op) { const u32 addr = GetAdjustedRenderTargetAddress(op); if (addr != (u32)-1) { return IsRenderTargetBreakpoint(addr); @@ -175,7 +143,7 @@ bool IsRenderTargetCmdBreakpoint(u32 op) { return false; } -static bool HitBreakpointCond(BreakpointInfo &bp, u32 op) { +static bool HitBreakpointCond(GPUBreakpoints::BreakpointInfo &bp, u32 op) { u8 cmd = op >> 24; // Temporarily set the value while running the breakpoint. @@ -192,7 +160,7 @@ static bool HitBreakpointCond(BreakpointInfo &bp, u32 op) { return result != 0; } -static bool HitAddressBreakpoint(u32 pc, u32 op) { +bool GPUBreakpoints::HitAddressBreakpoint(u32 pc, u32 op) { if (breakPCsCount == 0) return false; @@ -207,7 +175,7 @@ static bool HitAddressBreakpoint(u32 pc, u32 op) { return true; } -static bool HitOpBreakpoint(u32 op) { +bool GPUBreakpoints::HitOpBreakpoint(u32 op) { u8 cmd = op >> 24; if (!IsCmdBreakpoint(cmd)) return false; @@ -220,7 +188,7 @@ static bool HitOpBreakpoint(u32 op) { return true; } -bool IsBreakpoint(u32 pc, u32 op) { +bool GPUBreakpoints::IsBreakpoint(u32 pc, u32 op) { if (HitAddressBreakpoint(pc, op) || HitOpBreakpoint(op)) { return true; } @@ -236,7 +204,7 @@ bool IsBreakpoint(u32 pc, u32 op) { return false; } -bool IsAddressBreakpoint(u32 addr, bool &temp) { +bool GPUBreakpoints::IsAddressBreakpoint(u32 addr, bool &temp) { if (breakPCsCount == 0) { temp = false; return false; @@ -247,7 +215,7 @@ bool IsAddressBreakpoint(u32 addr, bool &temp) { return breakPCs.find(addr) != breakPCs.end(); } -bool IsAddressBreakpoint(u32 addr) { +bool GPUBreakpoints::IsAddressBreakpoint(u32 addr) { if (breakPCsCount == 0) { return false; } @@ -256,7 +224,7 @@ bool IsAddressBreakpoint(u32 addr) { return breakPCs.find(addr) != breakPCs.end(); } -bool IsTextureBreakpoint(u32 addr, bool &temp) { +bool GPUBreakpoints::IsTextureBreakpoint(u32 addr, bool &temp) { if (breakTexturesCount == 0) { temp = false; return false; @@ -267,7 +235,7 @@ bool IsTextureBreakpoint(u32 addr, bool &temp) { return breakTextures.find(addr) != breakTextures.end(); } -bool IsTextureBreakpoint(u32 addr) { +bool GPUBreakpoints::IsTextureBreakpoint(u32 addr) { if (breakTexturesCount == 0) { return false; } @@ -276,7 +244,7 @@ bool IsTextureBreakpoint(u32 addr) { return breakTextures.find(addr) != breakTextures.end(); } -bool IsRenderTargetBreakpoint(u32 addr, bool &temp) { +bool GPUBreakpoints::IsRenderTargetBreakpoint(u32 addr, bool &temp) { if (breakRenderTargetsCount == 0) { temp = false; return false; @@ -289,7 +257,7 @@ bool IsRenderTargetBreakpoint(u32 addr, bool &temp) { return breakRenderTargets.find(addr) != breakRenderTargets.end(); } -bool IsRenderTargetBreakpoint(u32 addr) { +bool GPUBreakpoints::IsRenderTargetBreakpoint(u32 addr) { if (breakRenderTargetsCount == 0) { return false; } @@ -300,24 +268,24 @@ bool IsRenderTargetBreakpoint(u32 addr) { return breakRenderTargets.find(addr) != breakRenderTargets.end(); } -bool IsOpBreakpoint(u32 op, bool &temp) { +bool GPUBreakpoints::IsOpBreakpoint(u32 op, bool &temp) { return IsCmdBreakpoint(op >> 24, temp); } -bool IsOpBreakpoint(u32 op) { +bool GPUBreakpoints::IsOpBreakpoint(u32 op) { return IsCmdBreakpoint(op >> 24); } -bool IsCmdBreakpoint(u8 cmd, bool &temp) { +bool GPUBreakpoints::IsCmdBreakpoint(u8 cmd, bool &temp) { temp = breakCmdsTemp[cmd]; return breakCmds[cmd]; } -bool IsCmdBreakpoint(u8 cmd) { +bool GPUBreakpoints::IsCmdBreakpoint(u8 cmd) { return breakCmds[cmd]; } -static bool HasAnyBreakpoints() { +bool GPUBreakpoints::HasAnyBreakpoints() const { if (breakPCsCount != 0 || breakTexturesCount != 0 || breakRenderTargetsCount != 0) return true; if (textureChangeTemp) @@ -331,7 +299,7 @@ static bool HasAnyBreakpoints() { return false; } -void AddAddressBreakpoint(u32 addr, bool temp) { +void GPUBreakpoints::AddAddressBreakpoint(u32 addr, bool temp) { std::lock_guard guard(breaksLock); if (temp) { @@ -347,10 +315,10 @@ void AddAddressBreakpoint(u32 addr, bool temp) { } breakPCsCount = breakPCs.size(); - g_hasBreakpoints = true; + hasBreakpoints_ = true; } -void AddCmdBreakpoint(u8 cmd, bool temp) { +void GPUBreakpoints::AddCmdBreakpoint(u8 cmd, bool temp) { if (temp) { if (!breakCmds[cmd]) { breakCmdsTemp[cmd] = true; @@ -366,10 +334,10 @@ void AddCmdBreakpoint(u8 cmd, bool temp) { breakCmdsInfo[cmd].isConditional = false; } } - g_hasBreakpoints = true; + hasBreakpoints_ = true; } -void AddTextureBreakpoint(u32 addr, bool temp) { +void GPUBreakpoints::AddTextureBreakpoint(u32 addr, bool temp) { std::lock_guard guard(breaksLock); if (temp) { @@ -383,10 +351,10 @@ void AddTextureBreakpoint(u32 addr, bool temp) { } breakTexturesCount = breakTextures.size(); - g_hasBreakpoints = true; + hasBreakpoints_ = true; } -void AddRenderTargetBreakpoint(u32 addr, bool temp) { +void GPUBreakpoints::AddRenderTargetBreakpoint(u32 addr, bool temp) { std::lock_guard guard(breaksLock); addr &= 0x001FFFF0; @@ -402,50 +370,50 @@ void AddRenderTargetBreakpoint(u32 addr, bool temp) { } breakRenderTargetsCount = breakRenderTargets.size(); - g_hasBreakpoints = true; + hasBreakpoints_ = true; } -void AddTextureChangeTempBreakpoint() { +void GPUBreakpoints::AddTextureChangeTempBreakpoint() { textureChangeTemp = true; - g_hasBreakpoints = true; + hasBreakpoints_ = true; } -void AddAnyTempBreakpoint() { +void GPUBreakpoints::AddAnyTempBreakpoint() { for (int i = 0; i < 256; ++i) { AddCmdBreakpoint(i, true); } - g_hasBreakpoints = true; + hasBreakpoints_ = true; } -void RemoveAddressBreakpoint(u32 addr) { +void GPUBreakpoints::RemoveAddressBreakpoint(u32 addr) { std::lock_guard guard(breaksLock); breakPCsTemp.erase(addr); breakPCs.erase(addr); breakPCsCount = breakPCs.size(); - g_hasBreakpoints = HasAnyBreakpoints(); + hasBreakpoints_ = HasAnyBreakpoints(); } -void RemoveCmdBreakpoint(u8 cmd) { +void GPUBreakpoints::RemoveCmdBreakpoint(u8 cmd) { std::lock_guard guard(breaksLock); breakCmdsTemp[cmd] = false; breakCmds[cmd] = false; - g_hasBreakpoints = HasAnyBreakpoints(); + hasBreakpoints_ = HasAnyBreakpoints(); } -void RemoveTextureBreakpoint(u32 addr) { +void GPUBreakpoints::RemoveTextureBreakpoint(u32 addr) { std::lock_guard guard(breaksLock); breakTexturesTemp.erase(addr); breakTextures.erase(addr); breakTexturesCount = breakTextures.size(); - g_hasBreakpoints = HasAnyBreakpoints(); + hasBreakpoints_ = HasAnyBreakpoints(); } -void RemoveRenderTargetBreakpoint(u32 addr) { +void GPUBreakpoints::RemoveRenderTargetBreakpoint(u32 addr) { std::lock_guard guard(breaksLock); addr &= 0x001FFFF0; @@ -454,17 +422,17 @@ void RemoveRenderTargetBreakpoint(u32 addr) { breakRenderTargets.erase(addr); breakRenderTargetsCount = breakRenderTargets.size(); - g_hasBreakpoints = HasAnyBreakpoints(); + hasBreakpoints_ = HasAnyBreakpoints(); } -void RemoveTextureChangeTempBreakpoint() { +void GPUBreakpoints::RemoveTextureChangeTempBreakpoint() { std::lock_guard guard(breaksLock); textureChangeTemp = false; - g_hasBreakpoints = HasAnyBreakpoints(); + hasBreakpoints_ = HasAnyBreakpoints(); } -static bool SetupCond(BreakpointInfo &bp, const std::string &expression, std::string *error) { +static bool SetupCond(GPUBreakpoints::BreakpointInfo &bp, const std::string &expression, std::string *error) { bool success = true; if (expression.length() != 0) { if (GPUDebugInitExpression(gpuDebug, expression.c_str(), bp.expression)) { @@ -482,7 +450,7 @@ static bool SetupCond(BreakpointInfo &bp, const std::string &expression, std::st return success; } -bool SetAddressBreakpointCond(u32 addr, const std::string &expression, std::string *error) { +bool GPUBreakpoints::SetAddressBreakpointCond(u32 addr, const std::string &expression, std::string *error) { // Must have one in the first place, make sure it's not temporary. AddAddressBreakpoint(addr); @@ -491,7 +459,7 @@ bool SetAddressBreakpointCond(u32 addr, const std::string &expression, std::stri return SetupCond(breakPCs[addr], expression, error); } -bool GetAddressBreakpointCond(u32 addr, std::string *expression) { +bool GPUBreakpoints::GetAddressBreakpointCond(u32 addr, std::string *expression) { std::lock_guard guard(breaksLock); auto entry = breakPCs.find(addr); if (entry != breakPCs.end() && entry->second.isConditional) { @@ -502,7 +470,7 @@ bool GetAddressBreakpointCond(u32 addr, std::string *expression) { return false; } -bool SetCmdBreakpointCond(u8 cmd, const std::string &expression, std::string *error) { +bool GPUBreakpoints::SetCmdBreakpointCond(u8 cmd, const std::string &expression, std::string *error) { // Must have one in the first place, make sure it's not temporary. AddCmdBreakpoint(cmd); @@ -510,7 +478,7 @@ bool SetCmdBreakpointCond(u8 cmd, const std::string &expression, std::string *er return SetupCond(breakCmdsInfo[cmd], expression, error); } -bool GetCmdBreakpointCond(u8 cmd, std::string *expression) { +bool GPUBreakpoints::GetCmdBreakpointCond(u8 cmd, std::string *expression) { if (breakCmds[cmd] && breakCmdsInfo[cmd].isConditional) { if (expression) { std::lock_guard guard(breaksLock); @@ -521,11 +489,11 @@ bool GetCmdBreakpointCond(u8 cmd, std::string *expression) { return false; } -void UpdateLastTexture(u32 addr) { +void GPUBreakpoints::UpdateLastTexture(u32 addr) { lastTexture = addr; } -void ClearAllBreakpoints() { +void GPUBreakpoints::ClearAllBreakpoints() { std::lock_guard guard(breaksLock); for (int i = 0; i < 256; ++i) { @@ -545,10 +513,10 @@ void ClearAllBreakpoints() { breakRenderTargetsCount = breakRenderTargets.size(); textureChangeTemp = false; - g_hasBreakpoints = false; + hasBreakpoints_ = false; } -void ClearTempBreakpoints() { +void GPUBreakpoints::ClearTempBreakpoints() { std::lock_guard guard(breaksLock); // Reset ones that were temporary back to non-breakpoints in the primary arrays. @@ -578,7 +546,24 @@ void ClearTempBreakpoints() { breakRenderTargetsCount = breakRenderTargets.size(); textureChangeTemp = false; - g_hasBreakpoints = HasAnyBreakpoints(); + hasBreakpoints_ = HasAnyBreakpoints(); +} + +bool GPUBreakpoints::ToggleCmdBreakpoint(const GECmdInfo &info) { + if (IsCmdBreakpoint(info.cmd)) { + RemoveCmdBreakpoint(info.cmd); + if (info.otherCmd) + RemoveCmdBreakpoint(info.otherCmd); + if (info.otherCmd2) + RemoveCmdBreakpoint(info.otherCmd2); + return false; + } + + AddCmdBreakpoint(info.cmd); + if (info.otherCmd) + AddCmdBreakpoint(info.otherCmd); + if (info.otherCmd2) + AddCmdBreakpoint(info.otherCmd2); + return true; } -} // namespace diff --git a/GPU/Debugger/Breakpoints.h b/GPU/Debugger/Breakpoints.h index 90bf5df08351..10de2f005d35 100644 --- a/GPU/Debugger/Breakpoints.h +++ b/GPU/Debugger/Breakpoints.h @@ -18,12 +18,20 @@ #pragma once #include +#include +#include +#include #include "Common/CommonTypes.h" +#include "Common/Math/expression_parser.h" +#include "GPU/Common/GPUDebugInterface.h" -namespace GPUBreakpoints { - -extern bool g_hasBreakpoints; +struct GECmdInfo; +class GPUBreakpoints { +public: + GPUBreakpoints() { + Init(); + } void Init(); bool IsBreakpoint(u32 pc, u32 op); @@ -64,4 +72,49 @@ extern bool g_hasBreakpoints; bool IsOpBreakpoint(u32 op, bool &temp); bool IsOpBreakpoint(u32 op); + + bool HasBreakpoints() const { + return hasBreakpoints_; + } + + struct BreakpointInfo { + bool isConditional = false; + PostfixExpression expression; + std::string expressionString; + }; + + bool ToggleCmdBreakpoint(const GECmdInfo &info); + +private: + void AddNonTextureTempBreakpoints(); + void CheckForTextureChange(u32 op, u32 addr); + bool HasAnyBreakpoints() const; + bool IsTextureCmdBreakpoint(u32 op); + bool IsRenderTargetCmdBreakpoint(u32 op); + bool HitAddressBreakpoint(u32 pc, u32 op); + bool HitOpBreakpoint(u32 op); + + std::mutex breaksLock; + + bool breakCmds[256]{}; + BreakpointInfo breakCmdsInfo[256]{}; + std::unordered_map breakPCs; + std::set breakTextures; + std::set breakRenderTargets; + // Small optimization to avoid a lock/lookup for the common case. + size_t breakPCsCount = 0; + size_t breakTexturesCount = 0; + size_t breakRenderTargetsCount = 0; + + // If these are set, the above are also, but they should be temporary. + bool breakCmdsTemp[256]; + std::set breakPCsTemp; + std::set breakTexturesTemp; + std::set breakRenderTargetsTemp; + bool textureChangeTemp = false; + + u32 lastTexture = 0xFFFFFFFF; + std::vector nonTextureCmds; + + bool hasBreakpoints_ = false; // cached value of HasAnyBreakpoints(). }; diff --git a/GPU/Debugger/Debugger.cpp b/GPU/Debugger/Debugger.cpp index e7dee230e7c2..f2d8d616b365 100644 --- a/GPU/Debugger/Debugger.cpp +++ b/GPU/Debugger/Debugger.cpp @@ -58,7 +58,7 @@ const char *BreakNextToString(BreakNext next) { } bool NeedsSlowInterpreter() { - return breakNext != BreakNext::NONE || GPUBreakpoints::g_hasBreakpoints; + return breakNext != BreakNext::NONE; } void ClearBreak() { @@ -72,19 +72,19 @@ BreakNext GetBreakNext() { return breakNext; } -void SetBreakNext(BreakNext next) { +void SetBreakNext(BreakNext next, GPUBreakpoints *breakpoints) { breakNext = next; breakAtCount = -1; if (next == BreakNext::TEX) { - GPUBreakpoints::AddTextureChangeTempBreakpoint(); + breakpoints->AddTextureChangeTempBreakpoint(); } else if (next == BreakNext::PRIM || next == BreakNext::COUNT) { - GPUBreakpoints::AddCmdBreakpoint(GE_CMD_PRIM, true); - GPUBreakpoints::AddCmdBreakpoint(GE_CMD_BEZIER, true); - GPUBreakpoints::AddCmdBreakpoint(GE_CMD_SPLINE, true); - GPUBreakpoints::AddCmdBreakpoint(GE_CMD_VAP, true); + breakpoints->AddCmdBreakpoint(GE_CMD_PRIM, true); + breakpoints->AddCmdBreakpoint(GE_CMD_BEZIER, true); + breakpoints->AddCmdBreakpoint(GE_CMD_SPLINE, true); + breakpoints->AddCmdBreakpoint(GE_CMD_VAP, true); } else if (next == BreakNext::CURVE) { - GPUBreakpoints::AddCmdBreakpoint(GE_CMD_BEZIER, true); - GPUBreakpoints::AddCmdBreakpoint(GE_CMD_SPLINE, true); + breakpoints->AddCmdBreakpoint(GE_CMD_BEZIER, true); + breakpoints->AddCmdBreakpoint(GE_CMD_SPLINE, true); } else if (next == BreakNext::DRAW) { // This is now handled by switching to BreakNext::PRIM when we encounter a flush. // This will take us to the following actual draw. @@ -105,7 +105,7 @@ void SetBreakCount(int c, bool relative) { } } -NotifyResult NotifyCommand(u32 pc) { +NotifyResult NotifyCommand(u32 pc, GPUBreakpoints *breakpoints) { u32 op = Memory::ReadUnchecked_U32(pc); u32 cmd = op >> 24; if (thisFlipNum != gpuStats.numFlips) { @@ -134,8 +134,8 @@ NotifyResult NotifyCommand(u32 pc) { isBreakpoint = true; } else if (breakNext == BreakNext::COUNT) { isBreakpoint = primsThisFrame == breakAtCount; - } else if (GPUBreakpoints::g_hasBreakpoints) { - isBreakpoint = GPUBreakpoints::IsBreakpoint(pc, op); + } else if (breakpoints->HasBreakpoints()) { + isBreakpoint = breakpoints->IsBreakpoint(pc, op); } if (isBreakpoint && pc == g_skipPcOnce) { @@ -146,7 +146,7 @@ NotifyResult NotifyCommand(u32 pc) { g_skipPcOnce = 0; if (isBreakpoint) { - GPUBreakpoints::ClearTempBreakpoints(); + breakpoints->ClearTempBreakpoints(); if (coreState == CORE_POWERDOWN || !gpuDebug) { breakNext = BreakNext::NONE; @@ -176,7 +176,7 @@ void NotifyFlush() { NOTICE_LOG(Log::GeDebugger, "Flush detected, breaking at next PRIM"); g_primAfterDraw = false; // Switch to PRIM mode. - SetBreakNext(BreakNext::PRIM); + SetBreakNext(BreakNext::PRIM, gpuDebug->GetBreakpoints()); } } } diff --git a/GPU/Debugger/Debugger.h b/GPU/Debugger/Debugger.h index 1c19b9b88570..fa99d039d612 100644 --- a/GPU/Debugger/Debugger.h +++ b/GPU/Debugger/Debugger.h @@ -36,7 +36,7 @@ enum class BreakNext { bool NeedsSlowInterpreter(); -void SetBreakNext(BreakNext next); +void SetBreakNext(BreakNext next, GPUBreakpoints *breakpoints); void SetBreakCount(int c, bool relative = false); BreakNext GetBreakNext(); const char *BreakNextToString(BreakNext next); @@ -48,7 +48,7 @@ enum class NotifyResult { }; // While debugging is active, these may block. -NotifyResult NotifyCommand(u32 pc); +NotifyResult NotifyCommand(u32 pc, GPUBreakpoints *breakpoints); void NotifyFlush(); void NotifyDisplay(u32 framebuf, u32 stride, int format); void NotifyBeginFrame(); diff --git a/GPU/Debugger/GECommandTable.cpp b/GPU/Debugger/GECommandTable.cpp index 73898fdc6ceb..97c34f181055 100644 --- a/GPU/Debugger/GECommandTable.cpp +++ b/GPU/Debugger/GECommandTable.cpp @@ -417,23 +417,3 @@ const GECmdInfo &GECmdInfoByCmd(GECommand reg) { _assert_msg_((reg & 0xFF) == reg, "Invalid reg"); return geCmdInfo[reg & 0xFF]; } - -bool ToggleBreakpoint(const GECmdInfo &info) { - using namespace GPUBreakpoints; - if (IsCmdBreakpoint(info.cmd)) { - RemoveCmdBreakpoint(info.cmd); - if (info.otherCmd) - RemoveCmdBreakpoint(info.otherCmd); - if (info.otherCmd2) - RemoveCmdBreakpoint(info.otherCmd2); - return false; - } - - AddCmdBreakpoint(info.cmd); - if (info.otherCmd) - AddCmdBreakpoint(info.otherCmd); - if (info.otherCmd2) - AddCmdBreakpoint(info.otherCmd2); - return true; -} - diff --git a/GPU/Debugger/GECommandTable.h b/GPU/Debugger/GECommandTable.h index cbe3091cffda..0cb58fb8e24e 100644 --- a/GPU/Debugger/GECommandTable.h +++ b/GPU/Debugger/GECommandTable.h @@ -130,5 +130,3 @@ struct GECmdInfo { bool GECmdInfoByName(const char *name, GECmdInfo &info); const GECmdInfo &GECmdInfoByCmd(GECommand reg); - -bool ToggleBreakpoint(const GECmdInfo &info); diff --git a/GPU/Debugger/State.h b/GPU/Debugger/State.h index aed98e897e33..a4bfbcc435f2 100644 --- a/GPU/Debugger/State.h +++ b/GPU/Debugger/State.h @@ -41,10 +41,7 @@ void FormatStateRow(GPUDebugInterface *debug, char *dest, size_t destSize, CmdFo void FormatVertCol(char *dest, size_t destSize, const GPUDebugVertex &vert, int col); void FormatVertColRaw(VertexDecoder *decoder, char *dest, size_t destSize, int row, int col); - // These are utilities used by the debugger vertex preview. - -// Later I hope to re-use more of the real logic. bool GetPrimPreview(u32 op, GEPrimitiveType &prim, std::vector &vertices, std::vector &indices, int &count); void DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]); void DescribePixelRGBA(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]); diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 6ff2d895c843..304173f064e4 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -632,7 +632,7 @@ void GPUCommon::PSPFrame() { bool GPUCommon::SlowRunLoop(DisplayList &list) { const bool dumpThisFrame = dumpThisFrame_; while (downcount > 0) { - GPUDebug::NotifyResult result = GPUDebug::NotifyCommand(list.pc); + GPUDebug::NotifyResult result = GPUDebug::NotifyCommand(list.pc, &breakpoints_); if (result == GPUDebug::NotifyResult::Execute) { recorder_.NotifyCommand(list.pc); @@ -791,7 +791,7 @@ DLResult GPUCommon::ProcessDLQueue() { // To enable breakpoints, we don't do fast matrix loads while debugger active. debugRecording_ = recorder_.IsActive(); - useFastRunLoop_ = !(dumpThisFrame_ || debugRecording_ || GPUDebug::NeedsSlowInterpreter()); + useFastRunLoop_ = !(dumpThisFrame_ || debugRecording_ || GPUDebug::NeedsSlowInterpreter() || breakpoints_.HasBreakpoints()); } else { resumingFromDebugBreak_ = false; // The bottom part of the gpuState loop below, that wasn't executed @@ -882,7 +882,7 @@ DLResult GPUCommon::ProcessDLQueue() { bool GPUCommon::ShouldSplitOverGe() const { // Check for debugger active. // We only need to do this if we want to be able to step through Ge display lists using the Ge debuggers. - return GPUDebug::NeedsSlowInterpreter(); + return GPUDebug::NeedsSlowInterpreter() || breakpoints_.HasBreakpoints(); } void GPUCommon::Execute_OffsetAddr(u32 op, u32 diff) { diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 999b55d43a09..f929db8febf0 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -13,6 +13,7 @@ #include "GPU/GPUCommon.h" #include "GPU/GPUState.h" #include "GPU/Debugger/Record.h" +#include "GPU/Debugger/Breakpoints.h" #include "GPU/Common/ShaderCommon.h" #include "GPU/Common/GPUDebugInterface.h" #include "GPU/GPUDefinitions.h" @@ -381,6 +382,9 @@ class GPUCommon : public GPUDebugInterface { GPURecord::Recorder *GetRecorder() override { return &recorder_; } + GPUBreakpoints *GetBreakpoints() override { + return &breakpoints_; + } protected: virtual void ClearCacheNextFrame() {} @@ -507,6 +511,7 @@ class GPUCommon : public GPUDebugInterface { std::string reportingFullInfo_; GPURecord::Recorder recorder_; + GPUBreakpoints breakpoints_; private: void DoExecuteCall(u32 target); diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index 30eb8869ed10..05cf4a376ec0 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -160,7 +160,7 @@ void ImGeDisasmView::Draw(GPUDebugInterface *gpuDebug) { // draw_list->AddText(liveStart, 0xFFFFFFFF, disMeta.liveInfo); // } - bool bp = GPUBreakpoints::IsAddressBreakpoint(addr); + bool bp = gpuDebug->GetBreakpoints()->IsAddressBreakpoint(addr); if (bp) { draw_list->AddCircleFilled(ImVec2(canvas_p0.x + lineHeight * 0.5f, lineStart.y + lineHeight * 0.5f), lineHeight * 0.45f, 0xFF0000FF, 12); } @@ -189,10 +189,10 @@ void ImGeDisasmView::Draw(GPUDebugInterface *gpuDebug) { if (pressed) { if (io.MousePos.x < canvas_p0.x + lineHeight) { // Toggle breakpoint - if (!GPUBreakpoints::IsAddressBreakpoint(dragAddr_)) { - GPUBreakpoints::AddAddressBreakpoint(dragAddr_); + if (!gpuDebug->GetBreakpoints()->IsAddressBreakpoint(dragAddr_)) { + gpuDebug->GetBreakpoints()->AddAddressBreakpoint(dragAddr_); } else { - GPUBreakpoints::RemoveAddressBreakpoint(dragAddr_); + gpuDebug->GetBreakpoints()->RemoveAddressBreakpoint(dragAddr_); } bpPopup_ = true; } else { @@ -208,7 +208,7 @@ void ImGeDisasmView::Draw(GPUDebugInterface *gpuDebug) { if (ImGui::BeginPopup("context")) { if (bpPopup_) { if (ImGui::MenuItem("Remove breakpoint", NULL, false)) { - GPUBreakpoints::RemoveAddressBreakpoint(dragAddr_); + gpuDebug->GetBreakpoints()->RemoveAddressBreakpoint(dragAddr_); } } else if (Memory::IsValid4AlignedAddress(dragAddr_)) { char buffer[64]; @@ -366,27 +366,27 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa } ImGui::SameLine(); if (ImGui::Button("Tex")) { - GPUDebug::SetBreakNext(GPUDebug::BreakNext::TEX); + GPUDebug::SetBreakNext(GPUDebug::BreakNext::TEX, gpuDebug->GetBreakpoints()); } ImGui::SameLine(); if (ImGui::Button("NonTex")) { - GPUDebug::SetBreakNext(GPUDebug::BreakNext::NONTEX); + GPUDebug::SetBreakNext(GPUDebug::BreakNext::NONTEX, gpuDebug->GetBreakpoints()); } ImGui::SameLine(); if (ImGui::Button("Prim")) { - GPUDebug::SetBreakNext(GPUDebug::BreakNext::PRIM); + GPUDebug::SetBreakNext(GPUDebug::BreakNext::PRIM, gpuDebug->GetBreakpoints()); } ImGui::SameLine(); if (ImGui::Button("Draw")) { - GPUDebug::SetBreakNext(GPUDebug::BreakNext::DRAW); + GPUDebug::SetBreakNext(GPUDebug::BreakNext::DRAW, gpuDebug->GetBreakpoints()); } ImGui::SameLine(); if (ImGui::Button("Curve")) { - GPUDebug::SetBreakNext(GPUDebug::BreakNext::CURVE); + GPUDebug::SetBreakNext(GPUDebug::BreakNext::CURVE, gpuDebug->GetBreakpoints()); } ImGui::SameLine(); if (ImGui::Button("Single step")) { - GPUDebug::SetBreakNext(GPUDebug::BreakNext::OP); + GPUDebug::SetBreakNext(GPUDebug::BreakNext::OP, gpuDebug->GetBreakpoints()); } if (disableStepButtons) { ImGui::EndDisabled(); @@ -414,7 +414,7 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa ImGui::Text("Step pending (waiting for CPU): %s", GPUDebug::BreakNextToString(GPUDebug::GetBreakNext())); ImGui::SameLine(); if (ImGui::Button("Cancel step")) { - GPUDebug::SetBreakNext(GPUDebug::BreakNext::NONE); + GPUDebug::SetBreakNext(GPUDebug::BreakNext::NONE, gpuDebug->GetBreakpoints()); } } } else { @@ -430,7 +430,7 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa const auto &list = gpuDebug->GetDisplayList(index); char title[64]; snprintf(title, sizeof(title), "List %d", list.id); - if (ImGui::CollapsingHeader(title)) { + if (ImGui::CollapsingHeader(title, ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Text("State: %s", DLStateToString(list.state)); ImGui::TextUnformatted("PC:"); ImGui::SameLine(); @@ -441,6 +441,11 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa if (list.pendingInterrupt) { ImGui::TextUnformatted("(Pending interrupt)"); } + if (list.stall) { + ImGui::TextUnformatted("Stall addr:"); + ImGui::SameLine(); + ImClickableAddress(list.pc, control, ImCmd::SHOW_IN_GE_DISASM); + } ImGui::Text("Stack depth: %d", (int)list.stackptr); ImGui::Text("BBOX result: %d", (int)list.bboxResult); } diff --git a/Windows/GEDebugger/CtrlDisplayListView.cpp b/Windows/GEDebugger/CtrlDisplayListView.cpp index 113c7f297a02..3c7e7c4129f4 100644 --- a/Windows/GEDebugger/CtrlDisplayListView.cpp +++ b/Windows/GEDebugger/CtrlDisplayListView.cpp @@ -217,7 +217,7 @@ void CtrlDisplayListView::onPaint(WPARAM wParam, LPARAM lParam) DeleteObject(backgroundPen); // display address/symbol - if (GPUBreakpoints::IsAddressBreakpoint(address)) + if (gpuDebug->GetBreakpoints()->IsAddressBreakpoint(address)) { textColor = 0x0000FF; int yOffset = std::max(-1,(rowHeight-14+1)/2); @@ -269,12 +269,12 @@ void CtrlDisplayListView::toggleBreakpoint() void CtrlDisplayListView::PromptBreakpointCond() { std::string expression; - GPUBreakpoints::GetAddressBreakpointCond(curAddress, &expression); + gpuDebug->GetBreakpoints()->GetAddressBreakpointCond(curAddress, &expression); if (!InputBox_GetString(GetModuleHandle(NULL), wnd, L"Expression", expression, expression)) return; std::string error; - if (!GPUBreakpoints::SetAddressBreakpointCond(curAddress, expression, &error)) + if (!gpuDebug->GetBreakpoints()->SetAddressBreakpointCond(curAddress, expression, &error)) MessageBox(wnd, ConvertUTF8ToWString(error).c_str(), L"Invalid expression", MB_OK | MB_ICONEXCLAMATION); } @@ -318,7 +318,7 @@ void CtrlDisplayListView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) if (button == 2) { HMENU menu = GetContextMenu(ContextMenuID::DISPLAYLISTVIEW); - EnableMenuItem(menu, ID_GEDBG_SETCOND, GPUBreakpoints::IsAddressBreakpoint(curAddress) ? MF_ENABLED : MF_GRAYED); + EnableMenuItem(menu, ID_GEDBG_SETCOND, gpuDebug->GetBreakpoints()->IsAddressBreakpoint(curAddress) ? MF_ENABLED : MF_GRAYED); switch (TriggerContextMenu(ContextMenuID::DISPLAYLISTVIEW, wnd, ContextPoint::FromEvent(lParam))) { diff --git a/Windows/GEDebugger/GEDebugger.cpp b/Windows/GEDebugger/GEDebugger.cpp index 4b86803cec40..9167530b0437 100644 --- a/Windows/GEDebugger/GEDebugger.cpp +++ b/Windows/GEDebugger/GEDebugger.cpp @@ -56,7 +56,6 @@ #include "GPU/Debugger/State.h" #include "GPU/Debugger/Stepping.h" -using namespace GPUBreakpoints; using namespace GPUStepping; enum PrimaryDisplayType { @@ -136,7 +135,7 @@ StepCountDlg::~StepCountDlg() { void StepCountDlg::Jump(int count, bool relative) { if (relative && count == 0) return; - GPUDebug::SetBreakNext(GPUDebug::BreakNext::COUNT); + GPUDebug::SetBreakNext(GPUDebug::BreakNext::COUNT, gpuDebug->GetBreakpoints()); GPUDebug::SetBreakCount(count, relative); }; @@ -680,9 +679,9 @@ void CGEDebugger::UpdatePrimaryPreview(const GPUgstate &state) { bufferResult = GPU_GetCurrentTexture(primaryBuffer_, textureLevel_, &primaryIsFramebuffer_); flags = TexturePreviewFlags(state); if (bufferResult) { - UpdateLastTexture(state.getTextureAddress(textureLevel_)); + gpuDebug->GetBreakpoints()->UpdateLastTexture(state.getTextureAddress(textureLevel_)); } else { - UpdateLastTexture((u32)-1); + gpuDebug->GetBreakpoints()->UpdateLastTexture((u32)-1); } } else { switch (PrimaryDisplayType(fbTabs->CurrentTabIndex())) { @@ -733,9 +732,9 @@ void CGEDebugger::UpdateSecondPreview(const GPUgstate &state) { } else { bufferResult = GPU_GetCurrentTexture(secondBuffer_, textureLevel_, &secondIsFramebuffer_); if (bufferResult) { - UpdateLastTexture(state.getTextureAddress(textureLevel_)); + gpuDebug->GetBreakpoints()->UpdateLastTexture(state.getTextureAddress(textureLevel_)); } else { - UpdateLastTexture((u32)-1); + gpuDebug->GetBreakpoints()->UpdateLastTexture((u32)-1); } } @@ -990,31 +989,31 @@ BOOL CGEDebugger::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { case WM_COMMAND: switch (LOWORD(wParam)) { case IDC_GEDBG_STEPDRAW: - GPUDebug::SetBreakNext(GPUDebug::BreakNext::DRAW); + GPUDebug::SetBreakNext(GPUDebug::BreakNext::DRAW, gpuDebug->GetBreakpoints()); break; case IDC_GEDBG_STEP: - GPUDebug::SetBreakNext(GPUDebug::BreakNext::OP); + GPUDebug::SetBreakNext(GPUDebug::BreakNext::OP, gpuDebug->GetBreakpoints()); break; case IDC_GEDBG_STEPTEX: - GPUDebug::SetBreakNext(GPUDebug::BreakNext::TEX); + GPUDebug::SetBreakNext(GPUDebug::BreakNext::TEX, gpuDebug->GetBreakpoints()); break; case IDC_GEDBG_STEPFRAME: - GPUDebug::SetBreakNext(GPUDebug::BreakNext::FRAME); + GPUDebug::SetBreakNext(GPUDebug::BreakNext::FRAME, gpuDebug->GetBreakpoints()); break; case IDC_GEDBG_STEPVSYNC: - GPUDebug::SetBreakNext(GPUDebug::BreakNext::VSYNC); + GPUDebug::SetBreakNext(GPUDebug::BreakNext::VSYNC, gpuDebug->GetBreakpoints()); break; case IDC_GEDBG_STEPPRIM: - GPUDebug::SetBreakNext(GPUDebug::BreakNext::PRIM); + GPUDebug::SetBreakNext(GPUDebug::BreakNext::PRIM, gpuDebug->GetBreakpoints()); break; case IDC_GEDBG_STEPCURVE: - GPUDebug::SetBreakNext(GPUDebug::BreakNext::CURVE); + GPUDebug::SetBreakNext(GPUDebug::BreakNext::CURVE, gpuDebug->GetBreakpoints()); break; case IDC_GEDBG_STEPCOUNT: @@ -1030,10 +1029,10 @@ BOOL CGEDebugger::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { u32 texAddr = state.getTextureAddress(textureLevel_); // TODO: Better interface that allows add/remove or something. if (InputBox_GetHex(GetModuleHandle(NULL), m_hDlg, L"Texture Address", texAddr, texAddr)) { - if (IsTextureBreakpoint(texAddr)) { - RemoveTextureBreakpoint(texAddr); + if (gpuDebug->GetBreakpoints()->IsTextureBreakpoint(texAddr)) { + gpuDebug->GetBreakpoints()->RemoveTextureBreakpoint(texAddr); } else { - AddTextureBreakpoint(texAddr); + gpuDebug->GetBreakpoints()->AddTextureBreakpoint(texAddr); } } } @@ -1048,10 +1047,10 @@ BOOL CGEDebugger::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { u32 fbAddr = state.getFrameBufRawAddress(); // TODO: Better interface that allows add/remove or something. if (InputBox_GetHex(GetModuleHandle(NULL), m_hDlg, L"Framebuffer Address", fbAddr, fbAddr)) { - if (IsRenderTargetBreakpoint(fbAddr)) { - RemoveRenderTargetBreakpoint(fbAddr); + if (gpuDebug->GetBreakpoints()->IsRenderTargetBreakpoint(fbAddr)) { + gpuDebug->GetBreakpoints()->RemoveRenderTargetBreakpoint(fbAddr); } else { - AddRenderTargetBreakpoint(fbAddr); + gpuDebug->GetBreakpoints()->AddRenderTargetBreakpoint(fbAddr); } } } @@ -1079,7 +1078,7 @@ BOOL CGEDebugger::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { SetDlgItemText(m_hDlg, IDC_GEDBG_TEXADDR, L""); SetDlgItemText(m_hDlg, IDC_GEDBG_PRIMCOUNTER, L""); - GPUDebug::SetBreakNext(GPUDebug::BreakNext::NONE); + GPUDebug::SetBreakNext(GPUDebug::BreakNext::NONE, gpuDebug->GetBreakpoints()); break; case IDC_GEDBG_RECORD: @@ -1127,24 +1126,24 @@ BOOL CGEDebugger::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { break; case WM_GEDBG_STEPDISPLAYLIST: - GPUDebug::SetBreakNext(GPUDebug::BreakNext::OP); + GPUDebug::SetBreakNext(GPUDebug::BreakNext::OP, gpuDebug->GetBreakpoints()); break; case WM_GEDBG_TOGGLEPCBREAKPOINT: { u32 pc = (u32)wParam; bool temp; - bool isBreak = IsAddressBreakpoint(pc, temp); + bool isBreak = gpuDebug->GetBreakpoints()->IsAddressBreakpoint(pc, temp); if (isBreak && !temp) { - if (GetAddressBreakpointCond(pc, nullptr)) { + if (gpuDebug->GetBreakpoints()->GetAddressBreakpointCond(pc, nullptr)) { int ret = MessageBox(m_hDlg, L"This breakpoint has a custom condition.\nDo you want to remove it?", L"Confirmation", MB_YESNO); if (ret == IDYES) - RemoveAddressBreakpoint(pc); + gpuDebug->GetBreakpoints()->RemoveAddressBreakpoint(pc); } else { - RemoveAddressBreakpoint(pc); + gpuDebug->GetBreakpoints()->RemoveAddressBreakpoint(pc); } } else { - AddAddressBreakpoint(pc); + gpuDebug->GetBreakpoints()->AddAddressBreakpoint(pc); } } break; @@ -1152,7 +1151,7 @@ BOOL CGEDebugger::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { case WM_GEDBG_RUNTOWPARAM: { u32 pc = (u32)wParam; - AddAddressBreakpoint(pc, true); + gpuDebug->GetBreakpoints()->AddAddressBreakpoint(pc, true); SendMessage(m_hDlg,WM_COMMAND,IDC_GEDBG_RESUME,0); } break; diff --git a/Windows/GEDebugger/TabState.cpp b/Windows/GEDebugger/TabState.cpp index 01c224058fd7..237ab0f379f3 100644 --- a/Windows/GEDebugger/TabState.cpp +++ b/Windows/GEDebugger/TabState.cpp @@ -38,8 +38,6 @@ #include "GPU/Debugger/Stepping.h" #include "GPU/Debugger/State.h" -using namespace GPUBreakpoints; - // First column is the breakpoint icon. static const GenericListViewColumn stateValuesCols[] = { { L"", 0.03f }, @@ -334,12 +332,12 @@ void CtrlStateValues::OnDoubleClick(int row, int column) { if (column == STATEVALUES_COL_BREAKPOINT) { bool proceed = true; - if (GetCmdBreakpointCond(info.cmd, nullptr)) { + if (gpuDebug->GetBreakpoints()->GetCmdBreakpointCond(info.cmd, nullptr)) { int ret = MessageBox(GetHandle(), L"This breakpoint has a custom condition.\nDo you want to remove it?", L"Confirmation", MB_YESNO); proceed = ret == IDYES; } if (proceed) - SetItemState(row, ToggleBreakpoint(info) ? 1 : 0); + SetItemState(row, gpuDebug->GetBreakpoints()->ToggleCmdBreakpoint(info) ? 1 : 0); return; } @@ -401,7 +399,7 @@ void CtrlStateValues::OnRightClick(int row, int column, const POINT &point) { HMENU subMenu = GetContextMenu(ContextMenuID::GEDBG_STATE); SetMenuDefaultItem(subMenu, ID_REGLIST_CHANGE, FALSE); - EnableMenuItem(subMenu, ID_GEDBG_SETCOND, GPUBreakpoints::IsCmdBreakpoint(info.cmd) ? MF_ENABLED : MF_GRAYED); + EnableMenuItem(subMenu, ID_GEDBG_SETCOND, gpuDebug->GetBreakpoints()->GPUBreakpoints::IsCmdBreakpoint(info.cmd) ? MF_ENABLED : MF_GRAYED); // Ehh, kinda ugly. if (!watchList.empty() && rows_ == &watchList[0]) { @@ -419,12 +417,12 @@ void CtrlStateValues::OnRightClick(int row, int column, const POINT &point) { { case ID_DISASM_TOGGLEBREAKPOINT: { bool proceed = true; - if (GetCmdBreakpointCond(info.cmd, nullptr)) { + if (gpuDebug->GetBreakpoints()->GetCmdBreakpointCond(info.cmd, nullptr)) { int ret = MessageBox(GetHandle(), L"This breakpoint has a custom condition.\nDo you want to remove it?", L"Confirmation", MB_YESNO); proceed = ret == IDYES; } if (proceed) - SetItemState(row, ToggleBreakpoint(info) ? 1 : 0); + SetItemState(row, gpuDebug->GetBreakpoints()->ToggleCmdBreakpoint(info) ? 1 : 0); break; } @@ -498,20 +496,19 @@ bool CtrlStateValues::RowValuesChanged(int row) { void CtrlStateValues::PromptBreakpointCond(const GECmdInfo &info) { std::string expression; - GPUBreakpoints::GetCmdBreakpointCond(info.cmd, &expression); + gpuDebug->GetBreakpoints()->GetCmdBreakpointCond(info.cmd, &expression); if (!InputBox_GetString(GetModuleHandle(NULL), GetHandle(), L"Expression", expression, expression)) return; std::string error; - if (!GPUBreakpoints::SetCmdBreakpointCond(info.cmd, expression, &error)) { + if (!gpuDebug->GetBreakpoints()->SetCmdBreakpointCond(info.cmd, expression, &error)) { MessageBox(GetHandle(), ConvertUTF8ToWString(error).c_str(), L"Invalid expression", MB_OK | MB_ICONEXCLAMATION); } else { if (info.otherCmd) - GPUBreakpoints::SetCmdBreakpointCond(info.otherCmd, expression, &error); + gpuDebug->GetBreakpoints()->SetCmdBreakpointCond(info.otherCmd, expression, &error); if (info.otherCmd2) - GPUBreakpoints::SetCmdBreakpointCond(info.otherCmd2, expression, &error); + gpuDebug->GetBreakpoints()->SetCmdBreakpointCond(info.otherCmd2, expression, &error); } - } TabStateValues::TabStateValues(const GECommand *rows, size_t rowCount, LPCSTR dialogID, HINSTANCE _hInstance, HWND _hParent) diff --git a/Windows/GEDebugger/TabVertices.cpp b/Windows/GEDebugger/TabVertices.cpp index 5d18b1d9d3f9..464471398e3c 100644 --- a/Windows/GEDebugger/TabVertices.cpp +++ b/Windows/GEDebugger/TabVertices.cpp @@ -437,16 +437,16 @@ void CtrlMatrixList::ToggleBreakpoint(int row) { return; // Okay, this command is in range. Toggle the actual breakpoint. - bool state = !GPUBreakpoints::IsCmdBreakpoint(info->cmd); + bool state = !gpuDebug->GetBreakpoints()->IsCmdBreakpoint(info->cmd); if (state) { - GPUBreakpoints::AddCmdBreakpoint(info->cmd); + gpuDebug->GetBreakpoints()->AddCmdBreakpoint(info->cmd); } else { - if (GPUBreakpoints::GetCmdBreakpointCond(info->cmd, nullptr)) { + if (gpuDebug->GetBreakpoints()->GetCmdBreakpointCond(info->cmd, nullptr)) { int ret = MessageBox(GetHandle(), L"This breakpoint has a custom condition.\nDo you want to remove it?", L"Confirmation", MB_YESNO); if (ret != IDYES) return; } - GPUBreakpoints::RemoveCmdBreakpoint(info->cmd); + gpuDebug->GetBreakpoints()->RemoveCmdBreakpoint(info->cmd); } for (int r = info->row; r < (info + 1)->row; ++r) { @@ -460,12 +460,12 @@ void CtrlMatrixList::PromptBreakpointCond(int row) { return; std::string expression; - GPUBreakpoints::GetCmdBreakpointCond(info->cmd, &expression); + gpuDebug->GetBreakpoints()->GetCmdBreakpointCond(info->cmd, &expression); if (!InputBox_GetString(GetModuleHandle(NULL), GetHandle(), L"Expression", expression, expression)) return; std::string error; - if (!GPUBreakpoints::SetCmdBreakpointCond(info->cmd, expression, &error)) + if (!gpuDebug->GetBreakpoints()->SetCmdBreakpointCond(info->cmd, expression, &error)) MessageBox(GetHandle(), ConvertUTF8ToWString(error).c_str(), L"Invalid expression", MB_OK | MB_ICONEXCLAMATION); } @@ -531,7 +531,7 @@ void CtrlMatrixList::OnRightClick(int row, int column, const POINT &point) { HMENU subMenu = GetContextMenu(ContextMenuID::GEDBG_MATRIX); SetMenuDefaultItem(subMenu, ID_REGLIST_CHANGE, FALSE); - EnableMenuItem(subMenu, ID_GEDBG_SETCOND, info && GPUBreakpoints::IsCmdBreakpoint(info->cmd) ? MF_ENABLED : MF_GRAYED); + EnableMenuItem(subMenu, ID_GEDBG_SETCOND, info && gpuDebug->GetBreakpoints()->IsCmdBreakpoint(info->cmd) ? MF_ENABLED : MF_GRAYED); switch (TriggerContextMenu(ContextMenuID::GEDBG_MATRIX, GetHandle(), ContextPoint::FromClient(point))) { case ID_DISASM_TOGGLEBREAKPOINT: From 8fdeb48498db5bc460ee2469a2c1ef69c5198485 Mon Sep 17 00:00:00 2001 From: DDinghoya Date: Sun, 15 Dec 2024 20:56:36 +0900 Subject: [PATCH 58/58] Update ko_KR.ini --- assets/lang/ko_KR.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/lang/ko_KR.ini b/assets/lang/ko_KR.ini index 64a39e77a4b1..26afdaaa4ac1 100644 --- a/assets/lang/ko_KR.ini +++ b/assets/lang/ko_KR.ini @@ -479,7 +479,7 @@ Error reading file = 파일 읽기 오류입니다. Failed initializing CPU/Memory = CPU 또는 메모리 초기화 실패 Failed to load executable: = 실행 파일 불러오기 실패: File corrupt = 파일 손상 -File not found: %1 = File not found: %1 +File not found: %1 = 파일을 찾을 수 없음: %1 Game disc read error - ISO corrupt = 게임 디스크 읽기 오류: ISO 손상되었습니다. GenericAllStartupError = PPSSPP가 그래픽 백엔드로 시작하지 못했습니다. 그래픽 및 기타 드라이버를 업그레이드해 보세요. GenericBackendSwitchCrash = 시작하는 동안 PPSSPP가 충돌했습니다.\n\n이것은 일반적으로 그래픽 드라이버 문제를 의미합니다. 그래픽 드라이버를 업그레이드해 보세요.\n\n그래픽 백엔드가 전환되었습니다: @@ -510,7 +510,7 @@ Running slow: try frameskip, sound is choppy when slow = 느리게 실행: 프 Running slow: Try turning off Software Rendering = 느리게 실행: "소프트웨어 렌더링" 끄기 시도 Save encryption failed. This save won't work on real PSP = 저장 암호화에 실패했습니다. 이 저장은 실제 PSP에서 작동하지 않습니다. textures.ini filenames may not be cross-platform = "textures.ini" 파일 이름은 크로스 플랫폼이 아닐 수 있습니다. -The file is not a valid zip file = 파일 읽기 오류입니다. +The file is not a valid zip file = 해당 파일은 유효한 zip 파일이 아닙니다. This is a saved state, not a game. = 이것은 게임이 아닌 저장된 상태입니다. This is save data, not a game. = 이것은 게임이 아닌 세이브 데이터입니다. Unable to create cheat file, disk may be full = 치트 파일을 생성할 수 없습니다. 디스크가 가득 찼을 수 있습니다.