Last active
October 24, 2022 09:38
-
-
Save thennequin/c75e1860b63e694ec918 to your computer and use it in GitHub Desktop.
Template numeric slider for ImGui
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Exemple: | |
float fValue = 1.f; | |
DragNumeric("Float", &fValue, 1.0, 0.f, 0.f, "%f"); | |
double fDoubleValue = 1.f; | |
DragNumeric("Double", &fDoubleValue, 1.0, 0.0, 0.0, "%lf"); | |
*/ | |
template<typename T> | |
static bool DragNumeric(const char* pLabel, T* pValue, double fSpeed, T oMin, T oMax, const char* pDisplayFormat) | |
{ | |
return DragNumeric<T>(pLabel, pValue, fSpeed, &oMin, &oMax, pDisplayFormat); | |
} | |
template<typename T> | |
static bool DragNumeric(const char* pLabel, T* pValue, double fSpeed, const T* pMin, const T* pMax, const char* pDisplayFormat) | |
{ | |
PA_ASSERT( pMin == NULL || pMax == NULL || *pMin <= *pMax ); | |
ImGuiWindow* pWindow = ImGui::GetCurrentWindow(); | |
if (pWindow->SkipItems) | |
return false; | |
ImGuiState& oState = *(ImGuiState*)ImGui::GetInternalState(); | |
const ImGuiStyle& oStyle = oState.Style; | |
const ImGuiID oId = ImGui::GetID(pLabel); | |
const float fWidth = ImGui::CalcItemWidth(); | |
const ImVec2 oLabelSize = ImGui::CalcTextSize(pLabel, NULL, true); | |
const ImRect oFrameBB(pWindow->DC.CursorPos, pWindow->DC.CursorPos + ImVec2(fWidth, oLabelSize.y) + oStyle.FramePadding*2.0f); | |
const ImRect oInnerBB(oFrameBB.Min + oStyle.FramePadding, oFrameBB.Max - oStyle.FramePadding); | |
const ImRect oTotalBB(oFrameBB.Min, oFrameBB.Max + ImVec2(oLabelSize.x > 0.0f ? oStyle.ItemInnerSpacing.x + oLabelSize.x : 0.0f, 0.0f)); | |
if (!ImGui::ItemAdd(oTotalBB, &oId)) | |
{ | |
ImGui::ItemSize(oTotalBB, oStyle.FramePadding.y); | |
return false; | |
} | |
const bool bHovered = ImGui::IsHovered(oFrameBB, oId); | |
if (bHovered) | |
ImGui::SetHoveredID(oId); | |
bool bStartTextInput = false; | |
const bool bTabFocusRequested = ImGui::FocusableItemRegister(pWindow, oState.ActiveId == oId); | |
if (bTabFocusRequested || (bHovered && (oState.IO.MouseClicked[0] | oState.IO.MouseDoubleClicked[0]))) | |
{ | |
ImGui::SetActiveID(oId, pWindow); | |
ImGui::FocusWindow(pWindow); | |
if (bTabFocusRequested || oState.IO.KeyCtrl || oState.IO.MouseDoubleClicked[0]) | |
{ | |
bStartTextInput = true; | |
oState.ScalarAsInputTextId = 0; | |
} | |
} | |
T oValueRange = T(); | |
if (fSpeed == 0.f) | |
{ | |
if (pMax != NULL && pMin != NULL) | |
{ | |
oValueRange = *pMax - *pMin; | |
} | |
else | |
{ | |
fSpeed = 1.0; | |
} | |
} | |
const float c_fGrabPadding = 2.0f; | |
const float fSliderSize = oFrameBB.GetWidth() - c_fGrabPadding * 2.0f; | |
const float fGrabSize = ImMin(oStyle.GrabMinSize, fSliderSize); | |
const float fSliderUsableSize = fSliderSize - fGrabSize; | |
const float fSliderUsablePosMin = oFrameBB.Min.x + c_fGrabPadding + fGrabSize * 0.5f; | |
const float fSliderUsablePosMax = oFrameBB.Max.x - c_fGrabPadding - fGrabSize * 0.5f; | |
bool bValueChanged = false; | |
if (bStartTextInput || (oState.ActiveId == oId && oState.ScalarAsInputTextId == oId)) | |
{ | |
ImGui::SetActiveID(oState.ScalarAsInputTextId, pWindow); | |
ImGui::SetHoveredID(0); | |
ImGui::FocusableItemUnregister(pWindow); | |
//TODO Text input | |
char pValueBuffer[256]; | |
ImFormatString(pValueBuffer, IM_ARRAYSIZE(pValueBuffer), pDisplayFormat, *pValue); | |
ImVec2 oArgSize = oFrameBB.GetSize(); | |
oArgSize.x -= oStyle.FramePadding.x * 2.0f; | |
oArgSize.y -= oStyle.FramePadding.y * 2.0f; | |
if (ImGui::InputTextEx(pLabel, pValueBuffer, IM_ARRAYSIZE(pValueBuffer), oArgSize, ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll)) | |
{ | |
T oNewValue[4]; //Array for memory overflow | |
if (sscanf(pValueBuffer, pDisplayFormat, oNewValue) == 1) | |
{ | |
if (pMax != NULL && oNewValue[0] > *pMax) | |
{ | |
oNewValue[0] = *pMax; | |
} | |
if (pMin != NULL && oNewValue[0] < *pMin) | |
{ | |
oNewValue[0] = *pMin; | |
} | |
*pValue = oNewValue[0]; | |
bValueChanged = true; | |
} | |
} | |
if (oState.ScalarAsInputTextId == 0) | |
{ | |
// First frame | |
IM_ASSERT(oState.ActiveId == oId); // InputText ID expected to match the Slider ID (else we'd need to store them both, which is also possible) | |
oState.ScalarAsInputTextId = oState.ActiveId; | |
ImGui::SetHoveredID(oId); | |
} | |
else if (oState.ActiveId != oState.ScalarAsInputTextId) | |
{ | |
// Release | |
oState.ScalarAsInputTextId = 0; | |
} | |
return bValueChanged; | |
} | |
else | |
{ | |
ImGui::ItemSize(oTotalBB, oStyle.FramePadding.y); | |
// Draw frame | |
const ImU32 iFrameColorIndex = pWindow->Color((oState.ActiveId == oId && fSpeed != 0.0) ? ImGuiCol_FrameBgActive : (oState.HoveredId == oId && fSpeed != 0.0) ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); | |
ImGui::RenderFrame(oFrameBB.Min, oFrameBB.Max, iFrameColorIndex, true, oStyle.FrameRounding); | |
if (oState.ActiveId == oId) | |
{ | |
if (oState.IO.MouseDown[0]) | |
{ | |
static T s_tCurrentDragValue; | |
if (fSpeed == 0.f) // Slider | |
{ | |
const float fNormalizePos = ImClamp((oState.IO.MousePos.x - fSliderUsablePosMin) / fSliderUsableSize, 0.0, 1.0); | |
*pValue = *pMin + (T)(oValueRange * fNormalizePos); | |
bValueChanged = true; | |
} | |
else // Drag | |
{ | |
if (oState.ActiveIdIsJustActivated) | |
{ | |
s_tCurrentDragValue = *pValue; | |
oState.DragLastMouseDelta = ImVec2(0.f, 0.f); | |
} | |
if (oState.IO.KeyShift && oState.DragSpeedScaleFast >= 0.0f) | |
fSpeed = fSpeed * oState.DragSpeedScaleFast; | |
if (oState.IO.KeyAlt && oState.DragSpeedScaleSlow >= 0.0f) | |
fSpeed = fSpeed * oState.DragSpeedScaleSlow; | |
const ImVec2 oMouseDragDelta = ImGui::GetMouseDragDelta(0, 1.0f); | |
float delta = (oMouseDragDelta.x - oState.DragLastMouseDelta.x); | |
T oNewValue = s_tCurrentDragValue + (T)(delta * fSpeed); | |
if (delta > 0.f) | |
{ | |
if (NULL != pMax && (oNewValue < s_tCurrentDragValue || oNewValue > *pMax)) | |
{ | |
oNewValue = *pMax; | |
} | |
} | |
else | |
{ | |
if (NULL != pMin && (oNewValue > s_tCurrentDragValue || oNewValue < *pMin)) | |
{ | |
oNewValue = *pMin; | |
} | |
} | |
if (oNewValue != s_tCurrentDragValue) | |
{ | |
oState.DragLastMouseDelta.x = oMouseDragDelta.x; | |
s_tCurrentDragValue = oNewValue; | |
*pValue = s_tCurrentDragValue; | |
bValueChanged = true; | |
} | |
} | |
} | |
else | |
{ | |
ImGui::SetActiveID(0, NULL); | |
} | |
} | |
} | |
if (fSpeed == 0.f) | |
{ | |
T oClampValue = *pValue - *pMin; | |
if (oClampValue < 0) oClampValue = 0; | |
if (oClampValue > oValueRange) oClampValue = oValueRange; | |
float fGrab = (float)((long double)oClampValue / (long double)oValueRange); | |
const float fGrabPos = fSliderUsablePosMin + (fSliderUsablePosMax - fSliderUsablePosMin) * fGrab; | |
ImRect oGrabBB = ImRect(ImVec2(fGrabPos - fGrabSize * 0.5f, oFrameBB.Min.y + c_fGrabPadding), ImVec2(fGrabPos + fGrabSize * 0.5f, oFrameBB.Max.y - c_fGrabPadding)); | |
pWindow->DrawList->AddRectFilled(oGrabBB.Min, oGrabBB.Max, pWindow->Color(oState.ActiveId == oId ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), oStyle.GrabRounding); | |
} | |
char pValueBuffer[256]; | |
const char* pValueBufferEnd = pValueBuffer + ImFormatString(pValueBuffer, IM_ARRAYSIZE(pValueBuffer), pDisplayFormat, *pValue); | |
ImGui::RenderTextClipped(oFrameBB.Min, oFrameBB.Max, pValueBuffer, pValueBufferEnd, NULL, ImGuiAlign_Center|ImGuiAlign_VCenter); | |
if (oLabelSize.x > 0.0f) | |
ImGui::RenderText(ImVec2(oFrameBB.Max.x + oStyle.ItemInnerSpacing.x, oInnerBB.Min.y), pLabel); | |
return bValueChanged; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment