Skip to content

Instantly share code, notes, and snippets.

@thennequin
Last active October 24, 2022 09:38
Show Gist options
  • Save thennequin/c75e1860b63e694ec918 to your computer and use it in GitHub Desktop.
Save thennequin/c75e1860b63e694ec918 to your computer and use it in GitHub Desktop.
Template numeric slider for ImGui
/*
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