Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Knob widget? #942

Closed
sonoro1234 opened this issue Dec 17, 2016 · 29 comments
Closed

Knob widget? #942

sonoro1234 opened this issue Dec 17, 2016 · 29 comments

Comments

@sonoro1234
Copy link

Hi,

Is there any plan to do a rotary knob widget?

Thanks

@ocornut
Copy link
Owner

ocornut commented Dec 17, 2016 via email

@sonoro1234
Copy link
Author

Ohh, I thought I could do it with custom drawing and then IsItemActive to capture mouse but I see that Dummy item always returns false.
Is there any sample using internal api?
Is there any better way to use from a luajit binding?

@ocornut
Copy link
Owner

ocornut commented Dec 20, 2016 via email

@sonoro1234
Copy link
Author

This is my luajit implementation of a knob with imgui (cimgui dialect)
Just wondering what would be the sensible way of getting colors, sz and spacing.
Another wonder is the InvisibleButton label (Is it relevant?)

Being able to do a widget with a script languaje makes me a complete imgui fan!!!

	 function knob(value_p,minv,maxv)
		
		local p = ig.GetCursorScreenPos();
        local col32 = imgui.igColorConvertFloat4ToU32(ig.ImVec4(1, 1, 0.4, 1));
		local col32line = imgui.igColorConvertFloat4ToU32(ig.ImVec4(1, 0, 0, 1));
		local col32text = imgui.igColorConvertFloat4ToU32(ig.ImVec4(0, 0, 0, 1));
		
		local sz = 36
		local x, y, spacing = p.x + 4.0,  p.y + 4.0, 8.0;
		
		local center = ig.ImVec2(x+sz*0.5, y+sz*0.5)
		local radio =  sz*0.5
		local val1 = (value_p[0] - minv)/(maxv - minv)
		local textval = string.format("%04.1f",value_p[0])
		local texsize = ig.CalcTextSize(textval, nil, true) 
		local textpos = ig.ImVec2(center.x - texsize.x*0.5, center.y - texsize.y*0.5)
		local gamma = math.pi/4 -- 0 value in knob
		local alpha = (math.pi-gamma)*val1*2+gamma
		
		local x2 = -math.sin(alpha)*radio + center.x
		local y2 = math.cos(alpha)*radio + center.y
		
		local draw_list = imgui.igGetWindowDrawList();

		imgui.ImDrawList_AddCircleFilled(draw_list, center, radio, col32, 32);
		imgui.ImDrawList_AddLine(draw_list, center, ig.ImVec2(x2, y2), col32line, 1);
		imgui.ImDrawList_AddText(draw_list, textpos, col32text, textval, nil);
		imgui.igInvisibleButton("s",ig.ImVec2((sz+spacing), (sz+spacing)))
		
		local touched = false
		if imgui.igIsItemActive() then 
			touched = true
			local mp = imgui.igGetIO().MousePos
			alpha = math.atan2(mp.x - center.x, center.y - mp.y) + math.pi
			alpha = math.max(gamma,math.min(2*math.pi-gamma,alpha))
			local value=0.5*(alpha-gamma)/(math.pi-gamma)
			value_p[0] = value*(maxv - minv) + minv
		end
		
		return touched
	end

@ocornut
Copy link
Owner

ocornut commented Dec 20, 2016

Cool :)
Please post a picture too!

The string is necessary to uniquely identify the widget using the id-stack system (read FAQ about id if unsure). Namely, it is used for the button press-hold-release mechanism.

For the color you can use ImGui::GetColorU32(ImGuiCol_Text) which will give you the current style color. Maybe that function isn't supported in cimgui or your Lua wrapper if you didn't find it.

I am not exactly sure what your spacing is exactly, but it may be style.FramePadding.x.

Being able to do a widget with a script languaje makes me a complete imgui fan!!!

It gets really cool once you master those lower-level bits. The only issue I can think of here is that a language like Lua may not scale very well in term of performance, if you have hundreds of widgets you might want to implement in in C++ and have a single call to a native function?

@ocornut
Copy link
Owner

ocornut commented Dec 20, 2016

I wrote a little demo widget using the public API.
It's not great really but better than nothing. The subtle problem with knobs is that there are five millions ways to make a knob. Someone could spend a week and write an entire knob library (imgui_knobs.h anyone?). Actually that would be a nice thing to do..

This is also exhibiting some of the issues with the public API which hasn't been used so much for custom widgets. There's no current public api to trivially do text clipping and alignment whereas the internal api has those. Hoping to improve the situation later but I'm still very unhappy with the internal text functions (they word but their api isn't fit for public api).

knobs

// Implementing a simple custom widget using the public API.
// You may also use the <imgui_internal.h> API to get raw access to more data/helpers, however the internal API isn't guaranteed to be forward compatible.
// FIXME: Need at least proper label centering + clipping (internal functions RenderTextClipped provides both but api is flaky/temporary)
static bool MyKnob(const char* label, float* p_value, float v_min, float v_max)
{
    ImGuiIO& io = ImGui::GetIO();
    ImGuiStyle& style = ImGui::GetStyle();

    float radius_outer = 20.0f;
    ImVec2 pos = ImGui::GetCursorScreenPos();
    ImVec2 center = ImVec2(pos.x + radius_outer, pos.y + radius_outer);
    float line_height = ImGui::GetTextLineHeight();
    ImDrawList* draw_list = ImGui::GetWindowDrawList();

    float ANGLE_MIN = 3.141592f * 0.75f;
    float ANGLE_MAX = 3.141592f * 2.25f;

    ImGui::InvisibleButton(label, ImVec2(radius_outer*2, radius_outer*2 + line_height + style.ItemInnerSpacing.y));
    bool value_changed = false;
    bool is_active = ImGui::IsItemActive();
    bool is_hovered = ImGui::IsItemActive();
    if (is_active && io.MouseDelta.x != 0.0f)
    {
        float step = (v_max - v_min) / 200.0f;
        *p_value += io.MouseDelta.x * step;
        if (*p_value < v_min) *p_value = v_min;
        if (*p_value > v_max) *p_value = v_max;
        value_changed = true;
    }

    float t = (*p_value - v_min) / (v_max - v_min);
    float angle = ANGLE_MIN + (ANGLE_MAX - ANGLE_MIN) * t;
    float angle_cos = cosf(angle), angle_sin = sinf(angle);
    float radius_inner = radius_outer*0.40f;
    draw_list->AddCircleFilled(center, radius_outer, ImGui::GetColorU32(ImGuiCol_FrameBg), 16);
    draw_list->AddLine(ImVec2(center.x + angle_cos*radius_inner, center.y + angle_sin*radius_inner), ImVec2(center.x + angle_cos*(radius_outer-2), center.y + angle_sin*(radius_outer-2)), ImGui::GetColorU32(ImGuiCol_SliderGrabActive), 2.0f);
    draw_list->AddCircleFilled(center, radius_inner, ImGui::GetColorU32(is_active ? ImGuiCol_FrameBgActive : is_hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);
    draw_list->AddText(ImVec2(pos.x, pos.y + radius_outer * 2 + style.ItemInnerSpacing.y), ImGui::GetColorU32(ImGuiCol_Text), label);

    if (is_active || is_hovered)
    {
        ImGui::SetNextWindowPos(ImVec2(pos.x - style.WindowPadding.x, pos.y - line_height - style.ItemInnerSpacing.y - style.WindowPadding.y));
        ImGui::BeginTooltip();
        ImGui::Text("%.3f", *p_value);
        ImGui::EndTooltip();
    }

    return value_changed;
}

@sonoro1234
Copy link
Author

Thanks for code, my new task will be testing performace of luajit vs a single call to MyKnob.

@ocornut
Copy link
Owner

ocornut commented Dec 20, 2016

(It is still totally overkill to use anti-aliased triangles to draw circles there, we're using like a hundred triangles there, I have a task to rasterize circles into the texture atlas to avoid that.)

@sonoro1234
Copy link
Author

I have tested luajit versus C++ implementation of knob
I made a window with 100 knobs and compared FPS showing and hiding the window
luajit 200/260
c++ 230/260

not so bad for luajit?

c++ code

bool Knob(const char* label, float* value_p, float minv, float maxv)
{	
	ImGuiStyle& style = ImGui::GetStyle();
	float line_height = ImGui::GetTextLineHeight();
	
	ImVec2 p = ImGui::GetCursorScreenPos();
	float sz = 36.0f;
	float radio =  sz*0.5f;
	ImVec2 center = ImVec2(p.x + radio, p.y + radio);
	float val1 = (value_p[0] - minv)/(maxv - minv);
	char textval[32];
	ImFormatString(textval, IM_ARRAYSIZE(textval), "%04.1f", value_p[0]);
	
	ImVec2 textpos = p;
	float gamma = M_PI/4.0f;//0 value in knob
	float alpha = (M_PI-gamma)*val1*2.0f+gamma;
	
	float x2 = -sinf(alpha)*radio + center.x;
	float y2 = cosf(alpha)*radio + center.y;
	
	ImGui::InvisibleButton(label,ImVec2(sz, sz + line_height + style.ItemInnerSpacing.y));

	bool is_active = ImGui::IsItemActive();
    bool is_hovered = ImGui::IsItemHovered();
	bool touched = false;
	
	if (is_active)
	{		
		touched = true;
		ImVec2 mp = ImGui::GetIO().MousePos;
		alpha = atan2f(mp.x - center.x, center.y - mp.y) + M_PI;
		alpha = ImMax(gamma,ImMin(2.0f*M_PI-gamma,alpha));
		float value = 0.5f*(alpha-gamma)/(M_PI-gamma);
		value_p[0] = value*(maxv - minv) + minv;
	}
	
	ImU32 col32 = ImGui::GetColorU32(is_active ? ImGuiCol_FrameBgActive : is_hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
	ImU32 col32line = ImGui::GetColorU32(ImGuiCol_SliderGrabActive); 
	ImU32 col32text = ImGui::GetColorU32(ImGuiCol_Text);
	ImDrawList* draw_list = ImGui::GetWindowDrawList();
	draw_list->AddCircleFilled(center, radio, col32, 16);
	draw_list->AddLine(center, ImVec2(x2, y2), col32line, 1);
	draw_list->AddText(textpos, col32text, textval);
	draw_list->AddText(ImVec2(p.x, p.y + sz + style.ItemInnerSpacing.y), col32text, label);
	
	return touched;
}

@sonoro1234
Copy link
Author

A very strange observation is that if this window is open on program start, FPS go as low as 100 and we get the expected 260 after hiding, showing and hiding the window. If window starts hidden this does not happen.
Any idea?

@ocornut
Copy link
Owner

ocornut commented Dec 22, 2016

luajit vs c++ looks quite ok here!

On the number themselves, assuming that you are early returning with Begin() returns false,
(1000/260) - (1000/230) = ~0.5 ms in my book that's a pretty bad/slow number for 100 knobs.

  • Is your build optimized. ?
  • The circle are currently quite heavy (until a further optimisation baking them in texture). Biggest win will happen when I add this much desired change.
  • Drawlist doesn't clip at all so it is advised widgets to do coarse culling. A normal widget would test for clipping and not render or interact at all when clipping (see ItemSize ItemAdd calls in internal code). I should allow those patterns to be expressed better with the public API.

On your mystery perf difference,

  • Are you testing the return value of Begin() to avoid rendering your window contents at all ?
    ImGui still does clipping but it is faster is you skip everything all together.
    Now, there could be a problem where the window size leads to different coarse clipping. Maybe using the Metrics to inspect the Window data while it is being closed before and after you can find some differences.

Thanks for querying, that sort of thing really ought to be improved!

@sonoro1234
Copy link
Author

Hello Omar,

Now I am making a dial (values represent angle) and I need current mouse_pos but also previous mouse pos.
As MousePosPrev is overwritten in NewFrame (This could be avoided doing it at the end of the frame , at the end of Render perhaps ) I have to find it with MouseDelta so that Mouseprevious = MousePos - MouseDelta.

I am worried because it is part of internal and you say "Forward compatibility not guaranteed!"

@ocornut
Copy link
Owner

ocornut commented Dec 23, 2016

Good point, thanks for asking. No worry about those two. I think they are here to stay and I expect lots of code has been relying on them. I may move them elsewhere in the function to clarify.

@ocornut
Copy link
Owner

ocornut commented Dec 23, 2016

I think I will also make it that the publicly exposed MousePosPrev is set to the expected value to be used. But for now MouseDelta works.

ocornut added a commit that referenced this issue Dec 23, 2016
(ImGuiIO structure layout changed)
@sonoro1234
Copy link
Author

Great!!

@turbo
Copy link

turbo commented Jul 2, 2018

So, what's the state of knobs in imgui? The demo you've posted looks quite promising.

(FR, I too am using Lua, but love-imgui and loving imgui so far)

@ocornut
Copy link
Owner

ocornut commented Jul 2, 2018

@turbo You can freely add this code and use it with your version of imgui. I don't think it'll be able to master very soon. I imagine in the future we'll add more widgets including perhaps some form of knobs but I also think the priority should be to stabilize the internal API and provide helpers to incentive people to build their own widgets.

@sonoro1234
Copy link
Author

Here is another knob in Lua with a InputFloat added
perhaps you have to adapt require"imgui" for love-imgui

local sin, cos, atan2, pi, max, min,acos,sqrt = math.sin, math.cos, math.atan2, math.pi, math.max, math.min,math.acos,math.sqrt

local function dial(label,value_p,sz, fac)
	local ig = require"imgui"
	local imgui = ig.lib

	fac = fac or 1
	sz = sz or 36
	local style = ig.GetStyle()
	
	local p = ig.GetCursorScreenPos();

	local radio =  sz*0.5
	local center = ig.ImVec2(p.x + radio, p.y + radio)
	
	local x2 = cos(value_p[0]/fac)*radio + center.x
	local y2 = sin(value_p[0]/fac)*radio + center.y
	
	ig.InvisibleButton(label.."t",ig.ImVec2(sz, sz)) 
	local is_active = ig.IsItemActive()
	local is_hovered = ig.IsItemHovered()
	
	local touched = false
	if is_active then 
		touched = true
		local m = ig.GetIO().MousePos
		local md = ig.GetIO().MouseDelta
		if md.x == 0 and md.y == 0 then touched=false end
		local mp = ig.ImVec2(m.x - md.x, m.y - md.y)
		local ax = mp.x - center.x
		local ay = mp.y - center.y
		local bx = m.x - center.x
		local by = m.y - center.y
		local ma = sqrt(ax*ax + ay*ay)
		local mb = sqrt(bx*bx + by*by)
		local ab  = ax * bx + ay * by;
		local vet = ax * by - bx * ay;
		ab = ab / (ma * mb);
		if not (ma == 0 or mb == 0 or ab < -1 or ab > 1) then

			if (vet>0) then
				value_p[0] = value_p[0] + acos(ab)*fac;
			else 
				value_p[0] = value_p[0] - acos(ab)*fac;
			end
		end
	end
	
	local col32idx = is_active and imgui.ImGuiCol_FrameBgActive or (is_hovered and imgui.ImGuiCol_FrameBgHovered or imgui.ImGuiCol_FrameBg)
	local col32 = ig.GetColorU32(col32idx, 1) 
	local col32line = ig.GetColorU32(imgui.ImGuiCol_SliderGrabActive, 1) 
	local draw_list = ig.GetWindowDrawList();
	draw_list:AddCircleFilled( center, radio, col32, 16);
	draw_list:AddLine( center, ig.ImVec2(x2, y2), col32line, 1);
	ig.SameLine()
	ig.PushItemWidth(50)
	if ig.InputFloat(label, value_p, 0.0, 0.1) then
		touched = true
	end
	ig.PopItemWidth()
	return touched
end

@hippyau
Copy link

hippyau commented Jan 6, 2022

Would love these in C++ :)

image

https://github.com/DGriffin91/imgui-rs-knobs

@ocornut
Copy link
Owner

ocornut commented Jan 6, 2022

Would love these in C++ :)

the code is small it should be easy to port back to C++

@frink
Copy link
Contributor

frink commented Jan 14, 2022

It would be great to add something like this into ImGUI proper. Knobs are a common request.

@ocornut If you're open to that I'll do the translation and PR...

@ocornut
Copy link
Owner

ocornut commented Jan 14, 2022

I think you can start convetting this into a standalone repo and we can decide late if they are suitable to merge into mainline.
Its perfectly fine to encourage other people to maintain small extensions ihmo.

@hippyau
Copy link

hippyau commented Feb 4, 2022

@frink I have not much to offer for your time... I could help test! But short on time to learn some RUST right now, and it looks familiar, but yeah I am a bit lost. I'd love these knobs and hundreds of ImGui users would too :) but I could offer a AUD$75 donation to the cause to bring them to everyone... or $100 if they get pulled (25% for ocornut's time). Not much I know, don't mean to offend. Or maybe one day I will learn to do myself... ¯_(ツ)_/¯ right now more focused on the use.

@hippyau
Copy link

hippyau commented Feb 4, 2022

Not suggesting it be merge-for-payment by the way... of course the code would need to be appropriate for maintainers.

@frink
Copy link
Contributor

frink commented Feb 10, 2022

My time is all of a sudden limited for a while. Sorry.

@altschuler
Copy link

I've made a C++ port of https://github.com/DGriffin91/imgui-rs-knobs available here https://github.com/altschuler/imgui-knobs. PRs, advice, review etc is very welcome.

@frink
Copy link
Contributor

frink commented Apr 25, 2022

Thanks @altschuler

@japajoe
Copy link

japajoe commented May 24, 2024

I've combined ocornut's visual style and sonoro's dragging behaviour, also added the option to snap the value (this is in C# but should be trivial to port to C++).

public static bool Knob(string label, ref float value, float min, float max, int snapSteps)
{
    ImGuiIOPtr io = ImGui.GetIO();
    ImGuiStylePtr style = ImGui.GetStyle();

    const float radiusOuter = 20.0f;
    Vector2 cursorPosition = ImGui.GetCursorScreenPos();
    Vector2 center = new Vector2(cursorPosition.X + radiusOuter, cursorPosition.Y + radiusOuter);
    float lineHeight = ImGui.GetTextLineHeight();
    ImDrawListPtr drawList = ImGui.GetWindowDrawList();

    ImGui.InvisibleButton(label, new Vector2(radiusOuter * 2, radiusOuter * 2 + lineHeight + style.ItemInnerSpacing.Y));
    bool valueChanged = false;
    bool isActive = ImGui.IsItemActive();
    bool isHovered = ImGui.IsItemActive();

    float t = (value - min) / (max - min);

    float gamma = MathF.PI / 4.0f;
    float alpha = (MathF.PI - gamma) * t * 2.0f + gamma;

    if(isActive && io.MouseDelta.Length() > 0.0f)
    {
        Vector2 mousePosition = ImGui.GetIO().MousePos;
        alpha = MathF.Atan2(mousePosition.X - center.X, center.Y - mousePosition.Y) + MathF.PI;
        alpha = MathF.Max(gamma, MathF.Min(2.0f * MathF.PI - gamma, alpha));
        float val = 0.5f * (alpha - gamma) / (MathF.PI - gamma);

        if(snapSteps > 0)
        {
            float stepSize = (max - min) / snapSteps;
            float snappedValue = min + MathF.Round(val * snapSteps) * stepSize;
            value = (float)Math.Clamp(snappedValue, min, max);
        }
        else
        {
            value = val * (max - min) + min;
        }

        valueChanged = true;
    }

    const float ANGLE_MIN = 3.141592f * 0.75f;
    const float ANGLE_MAX = 3.141592f * 2.25f;

    float angle = ANGLE_MIN + (ANGLE_MAX - ANGLE_MIN) * t;
    float angleCos = MathF.Cos(angle), angle_sin = MathF.Sin(angle);
    float radiusInner = radiusOuter * 0.40f;
    drawList.AddCircleFilled(center, radiusOuter, ImGui.GetColorU32(ImGuiCol.FrameBg), 16);
    drawList.AddLine(new Vector2(center.X + angleCos * radiusInner, center.Y + angle_sin * radiusInner), new Vector2(center.X + angleCos * (radiusOuter - 2), center.Y + angle_sin * (radiusOuter - 2)), ImGui.GetColorU32(ImGuiCol.SliderGrabActive), 2.0f);
    drawList.AddCircleFilled(center, radiusInner, ImGui.GetColorU32(isActive ? ImGuiCol.FrameBgActive : isHovered ? ImGuiCol.FrameBgHovered : ImGuiCol.FrameBg), 16);
    drawList.AddText(new Vector2(cursorPosition.X, cursorPosition.Y + radiusOuter * 2 + style.ItemInnerSpacing.Y), ImGui.GetColorU32(ImGuiCol.Text), label);

    if (isActive || isHovered)
    {
        string text = value.ToString("0.000");
        ImGui.SetNextWindowPos(new Vector2(cursorPosition.X - style.WindowPadding.X, cursorPosition.Y - lineHeight - style.ItemInnerSpacing.Y - style.WindowPadding.Y));
        ImGui.BeginTooltip();
        ImGui.Text(text);
        ImGui.EndTooltip();
    }

    return valueChanged;
}

@japajoe
Copy link

japajoe commented Jun 18, 2024

Here is another version that uses a spritesheet.

struct ImKnobInfo
{
    ImTextureID textureId;
    int numberOfSprites;
    int rows;
    int columns;
    ImKnobInfo(ImTextureID textureId, int numberOfSprites, int rows, int columns)
    {
        this->textureId = textureId;
        this->numberOfSprites = numberOfSprites;
        this->rows = rows;
        this->columns = columns;
    }
};

bool Knob(const char *label, const ImKnobInfo &knobInfo, const ImVec2 &size, float *value, float min, float max, int snapSteps)
{
    ImVec2 cursorPosition = ImGui::GetCursorScreenPos();
    ImVec2 center(cursorPosition.x + (size.x * 0.5f), cursorPosition.y + (size.y * 0.5f));
    float lineHeight = ImGui::GetTextLineHeight();

    auto &style = ImGui::GetStyle();
    ImGui::InvisibleButton(label, ImVec2(size.x, size.y + lineHeight + style.ItemInnerSpacing.y));
    bool valueChanged = false;
    bool isActive = ImGui::IsItemActive();
    bool isHovered = ImGui::IsItemHovered();
    bool isDragging = ImGui::IsMouseDragging(ImGuiMouseButton_Left);

    float t = (*value - min) / (max - min);
    float gamma = M_PI / 4.0f;
    float alpha = (M_PI - gamma) * t * 2.0f + gamma;

    auto clamp = [] (float v, float mn, float mx) {
        return v < mn ? mn : (v > mx ? mx : v);
    };

    if(isActive && isDragging)
    {
        auto &io = ImGui::GetIO();
        ImVec2 mousePosition = io.MousePos;
        alpha = atan2(mousePosition.x - center.x, center.y - mousePosition.y) + M_PI;
        alpha = fmax(gamma, fmin(2.0f * M_PI - gamma, alpha));
        float val = 0.5f * (alpha - gamma) / (M_PI - gamma);

        if(snapSteps > 0)
        {
            float stepSize = (max - min) / snapSteps;
            float snappedValue = min + round(val * snapSteps) * stepSize;
            *value = (float)clamp(snappedValue, min, max);
        }
        else
        {
            *value = val * (max - min) + min;
        }

        valueChanged = true;
    }

    auto getUVCoordinates = [] (float percentage, size_t numSprites, int rows, int columns, float *uv0, float *uv1) {
        size_t index = static_cast<size_t>(floor(percentage * (numSprites-1)));

        float spriteWidth = 1.0f / columns;
        float spriteHeight = 1.0f / rows;

        int colIndex = index % columns;
        int rowIndex = index / columns;

        uv0[0] = colIndex * spriteWidth;
        uv0[1] = rowIndex * spriteHeight;

        uv1[0] = uv0[0] + spriteWidth;
        uv1[1] = uv0[1] + spriteHeight;
    };

    ImVec2 uv0, uv1;
    getUVCoordinates(t, knobInfo.numberOfSprites -1, knobInfo.rows, knobInfo.columns, &uv0.x, &uv1.x);

    ImVec2 pMin = cursorPosition;
    ImVec2 pMax(pMin.x + size.x, pMin.y + size.y);
    auto drawList = ImGui::GetWindowDrawList();
    drawList->AddImage(knobInfo.textureId, pMin, pMax, uv0, uv1);

    if (isActive || isHovered)
    {
        ImGui::SetNextWindowPos(ImVec2(cursorPosition.x - style.WindowPadding.x, cursorPosition.y - lineHeight - style.ItemInnerSpacing.y - style.WindowPadding.y));
        ImGui::BeginTooltip();
        ImGui::Text("%s %.3f", label, *value);
        ImGui::EndTooltip();
    }

    return valueChanged;
}

I've used this sheet for testing which I got from Knobman.
Knob70_89
Example use:

// There are a total of 89 separate knob images.
// The image has 9 rows and 10 columns.
ImKnobInfo knobInfo(knobsTextureId, 89, 9, 10);

ImGui::Begin("Audio Settings");

float masterVolume = GetMasterVolume();

if(Knob("Master Volume", knobInfo, ImVec2(64, 64), &masterVolume, 0.0f, 1.0f, 0))
    SetMasterVolume(masterVolume);

ImGui::End();

# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

No branches or pull requests

7 participants