Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# ImGui Knobs
This is a port/adaptation of [imgui-rs-knobs](https://github.com/DGriffin91/imgui-rs-knobs), for C++.
I have made some changes to [imgui-knobs](https://github.com/altschuler/imgui-knobs), which is a port/adaptation of [imgui-rs-knobs](https://github.com/DGriffin91/imgui-rs-knobs), for C++.

![image](https://user-images.githubusercontent.com/956928/164050142-96a8dde4-7d2e-43e4-9afe-14ab48eac243.png)

Expand All @@ -18,7 +18,7 @@ if (ImGuiKnobs::Knob("Volume", &value, -6.0f, 6.0f, 0.1f, "%.1fdB", ImGuiKnobVar
Draw knobs using either `Knob` or `KnobInt`. The API is:

```
bool ImGuiKnobs::Knob(label, *value, min, max, [speed, format, variant, size, flags, steps])
bool ImGuiKnobs::Knob(label, *value, min, max, [speed, format, variant, size, flags, steps, angle_min, angle_max])
bool ImGuiKnobs::KnobInt(label, *value, min, max, [speed, format, variant, size, flags, steps])
```

Expand All @@ -30,6 +30,9 @@ bool ImGuiKnobs::KnobInt(label, *value, min, max, [speed, format, variant, size,
- `ImGuiKnobFlags_NoInput`: Hide the bottom drag input.
- `ImGuiKnobFlags_ValueTooltip`: Show a tooltip with the current value on hover.
- `ImGuiKnobFlags_DragHorizontal`: Use horizontal dragging (default is vertical).
- `ImGuiKnobFlags_RotateRelative`: Use rotate relative dragging.
- `ImGuiKnobFlags_RotateAbsolute`: Use rotate absolute dragging.
- `ImGuiKnobFlags_WrapAround`: Values wraparound when using the "rotate" flags.

### Size
You can specify a size given as the width of the knob (will be scaled according to ImGui's `FontGlobalScale`). Default (0) will use 4x line height.
Expand Down
197 changes: 180 additions & 17 deletions imgui-knobs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,39 @@

#include <cmath>
#include <cstdlib>
#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui.h>
#include <imgui_internal.h>

#define IMGUIKNOBS_PI 3.14159265358979323846f



namespace ImGuiKnobs {
namespace detail {


// Re-Maps clamped
template<typename T>
inline T map_range_clamped(const T& value, const T& in_start, const T& in_stop, const T& out_start, const T& out_stop) {
if (value <= in_start) {
return out_start;
}
else if (value >= in_stop) {
return out_stop;
}
return out_start + (out_stop - out_start) * ((value - in_start) / (in_stop - in_start));
}

inline float get_angle(const ImVec2& point1, const ImVec2& point2) {
ImVec2 dir(point2.x - point1.x, point2.y - point1.y);
float angle = atan2(-dir.y, -dir.x);
angle += (IMGUIKNOBS_PI * 2.5f);
angle = fmod(angle, IMGUIKNOBS_PI * 2.0f);

return angle;
}

void draw_arc1(ImVec2 center, float radius, float start_angle, float end_angle, float thickness, ImColor color, int num_segments) {
ImVec2 start = {
center[0] + cosf(start_angle) * radius,
Expand All @@ -33,7 +59,7 @@ namespace ImGuiKnobs {

auto *draw_list = ImGui::GetWindowDrawList();

draw_list->AddBezierCurve(start, arc1, arc2, end, color, thickness, num_segments);
draw_list->AddBezierCubic(start, arc1, arc2, end, color, thickness, num_segments);
}

void draw_arc(ImVec2 center, float radius, float start_angle, float end_angle, float thickness, ImColor color, int num_segments, int bezier_count) {
Expand All @@ -53,7 +79,8 @@ namespace ImGuiKnobs {
}

template<typename DataType>
struct knob {
struct knob
{
float radius;
bool value_changed;
ImVec2 center;
Expand All @@ -66,23 +93,155 @@ namespace ImGuiKnobs {
float angle_cos;
float angle_sin;

knob(const char *_label, ImGuiDataType data_type, DataType *p_value, DataType v_min, DataType v_max, float speed, float _radius, const char *format, ImGuiKnobFlags flags) {

bool wrap_around(DataType p_value,DataType prev_value, DataType v_min, DataType v_max) const noexcept
{
static const float kEpsilon = 0.05f;

if ( (p_value >= v_max * (1.0f - kEpsilon) && prev_value <= v_min * (1.0f + kEpsilon))
|| (p_value <= v_min * (1.0f + kEpsilon) && prev_value >= v_max * (1.0f - kEpsilon)))
{
return true;
}

return false;
}

bool rotate_behavior(ImGuiID id,DataType *p_value, DataType v_min, DataType v_max,float speed
,bool absolute_rot,bool aWrapAround)
{
ImGuiIO& imgui_io=ImGui::GetIO();

ImGuiContext& g = *GImGui;
if (g.ActiveId == id)
{
if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
ImGui::ClearActiveID();
else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
ImGui::ClearActiveID();
}

if (g.ActiveId != id)
return false;

if (absolute_rot)
{
ImVec2 mouse_pos = imgui_io.MousePos;
float input_angle = get_angle(center ,mouse_pos);
DataType prev_value = *p_value;

*p_value = static_cast<DataType>(map_range_clamped(input_angle,angle_min,angle_max, 1.0f * v_min, 1.0f * v_max));
if(!aWrapAround)
{
if( wrap_around(*p_value, prev_value, v_min, v_max) || fabs(*p_value - prev_value) > (v_min + v_max) * 0.75f)
{
*p_value = prev_value;
}
}

value_changed = (prev_value != *p_value);
}
else
{
ImVec2 mouse_pos, mouse_pos_prev;

if(imgui_io.MouseDownDuration[0] > 0.0f)
{
mouse_pos = imgui_io.MousePos;

mouse_pos_prev = imgui_io.MousePos;
mouse_pos_prev.x -= imgui_io.MouseDelta.x;
mouse_pos_prev.y -= imgui_io.MouseDelta.y;
}
else
{
mouse_pos = imgui_io.MouseClickedPos[0];
mouse_pos_prev = imgui_io.MouseClickedPos[0];
}

float input_angle_prev = get_angle(center,mouse_pos_prev);
float input_angle = get_angle(center,mouse_pos);

if (input_angle_prev != input_angle)
{
DataType prev_value = *p_value;
//if (!speed) // speed == 0 is changed in knob_with_drag.
{
speed = (v_max-v_min) * IMGUIKNOBS_PI * 0.4f;
}

if(!aWrapAround)
{
*p_value += static_cast<DataType>(map_range_clamped(input_angle - input_angle_prev, -2.0f * IMGUIKNOBS_PI, 2.0f * IMGUIKNOBS_PI, -speed, speed));
if (*p_value < v_min)
*p_value = v_min;
if (*p_value > v_max)
*p_value = v_max;

if (wrap_around(*p_value, prev_value, v_min, v_max) || fabs(input_angle - input_angle_prev) > IMGUIKNOBS_PI)
{
*p_value = prev_value;
value_changed = false;
}
else
{
value_changed = true;
}
}
else
{
*p_value += static_cast<DataType>(input_angle-input_angle_prev);
if(*p_value < v_min)
{
*p_value = v_max-(v_min-*p_value);
}
if(*p_value > v_max)
{
*p_value = v_min+(*p_value-v_max);
}

value_changed = true;
}
}
else
{
value_changed = false;
}
}

return value_changed;
}


knob(const char *_label, ImGuiDataType data_type, DataType *p_value, DataType v_min, DataType v_max, float speed, float _radius, const char *format, ImGuiKnobFlags flags
,float aAngleMin=IMGUIKNOBS_PI*0.75f,float aAngleMax=IMGUIKNOBS_PI*2.25f)
{
radius = _radius;
angle_min = aAngleMin;
angle_max = aAngleMax;
t = ((float) *p_value - v_min) / (v_max - v_min);
auto screen_pos = ImGui::GetCursorScreenPos();
center = {screen_pos[0] + radius, screen_pos[1] + radius};

// Handle dragging
ImGui::InvisibleButton(_label, {radius * 2.0f, radius * 2.0f});
auto gid = ImGui::GetID(_label);
ImGuiSliderFlags drag_flags = 0;
if (!(flags & ImGuiKnobFlags_DragHorizontal)) {
drag_flags |= ImGuiSliderFlags_Vertical;
}
value_changed = ImGui::DragBehavior(gid, data_type, p_value, speed, &v_min, &v_max, format, drag_flags);

angle_min = IMGUIKNOBS_PI * 0.75f;
angle_max = IMGUIKNOBS_PI * 2.25f;
center = {screen_pos[0] + radius, screen_pos[1] + radius};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this moved?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The center value is needed in RotateBehavior.

if((flags & (ImGuiKnobFlags_RotateRelative | ImGuiKnobFlags_RotateAbsolute)))
{
value_changed = rotate_behavior(gid,p_value, v_min, v_max, speed
, flags & ImGuiKnobFlags_RotateAbsolute
, flags & ImGuiKnobFlags_WrapAround);
}
else
{
ImGuiSliderFlags drag_flags = 0;
if (!(flags & ImGuiKnobFlags_DragHorizontal))
drag_flags |= ImGuiSliderFlags_Vertical;

value_changed = ImGui::DragBehavior(gid, data_type, p_value, speed, &v_min, &v_max, format, drag_flags);
}

is_active = ImGui::IsItemActive();
is_hovered = ImGui::IsItemHovered();
angle = angle_min + (angle_max - angle_min) * t;
Expand Down Expand Up @@ -140,7 +299,8 @@ namespace ImGuiKnobs {
};

template<typename DataType>
knob<DataType> knob_with_drag(const char *label, ImGuiDataType data_type, DataType *p_value, DataType v_min, DataType v_max, float _speed, const char *format, float size, ImGuiKnobFlags flags) {
knob<DataType> knob_with_drag(const char *label, ImGuiDataType data_type, DataType *p_value, DataType v_min, DataType v_max, float _speed, const char *format, float size, ImGuiKnobFlags flags
,float aAngleMin=IMGUIKNOBS_PI*0.75f,float aAngleMax=IMGUIKNOBS_PI*2.25f) {
auto speed = _speed == 0 ? (v_max - v_min) / 250.f : _speed;
ImGui::PushID(label);
auto width = size == 0 ? ImGui::GetTextLineHeight() * 4.0f : size * ImGui::GetIO().FontGlobalScale;
Expand All @@ -163,7 +323,8 @@ namespace ImGuiKnobs {
}

// Draw knob
knob<DataType> k(label, data_type, p_value, v_min, v_max, speed, width * 0.5f, format, flags);
knob<DataType> k(label, data_type, p_value, v_min, v_max, speed, width * 0.5f, format, flags
,aAngleMin,aAngleMax);

// Draw tooltip
if (flags & ImGuiKnobFlags_ValueTooltip && (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) || ImGui::IsItemActive())) {
Expand Down Expand Up @@ -223,8 +384,9 @@ namespace ImGuiKnobs {


template<typename DataType>
bool BaseKnob(const char *label, ImGuiDataType data_type, DataType *p_value, DataType v_min, DataType v_max, float speed, const char *format, ImGuiKnobVariant variant, float size, ImGuiKnobFlags flags, int steps = 10) {
auto knob = detail::knob_with_drag(label, data_type, p_value, v_min, v_max, speed, format, size, flags);
bool BaseKnob(const char *label, ImGuiDataType data_type, DataType *p_value, DataType v_min, DataType v_max, float speed, const char *format, ImGuiKnobVariant variant, float size, ImGuiKnobFlags flags, int steps = 10
,float aAngleMin=IMGUIKNOBS_PI*0.75f,float aAngleMax=IMGUIKNOBS_PI*2.25f) {
auto knob = detail::knob_with_drag(label, data_type, p_value, v_min, v_max, speed, format, size, flags,aAngleMin,aAngleMax);

switch (variant) {
case ImGuiKnobVariant_Tick: {
Expand Down Expand Up @@ -287,9 +449,10 @@ namespace ImGuiKnobs {
return knob.value_changed;
}

bool Knob(const char *label, float *p_value, float v_min, float v_max, float speed, const char *format, ImGuiKnobVariant variant, float size, ImGuiKnobFlags flags, int steps) {
bool Knob(const char *label, float *p_value, float v_min, float v_max, float speed, const char *format, ImGuiKnobVariant variant, float size, ImGuiKnobFlags flags, int steps
,float aAngleMin,float aAngleMax) {
const char *_format = format == NULL ? "%.3f" : format;
return BaseKnob(label, ImGuiDataType_Float, p_value, v_min, v_max, speed, _format, variant, size, flags, steps);
return BaseKnob(label,ImGuiDataType_Float,p_value,v_min,v_max,speed,_format,variant,size,flags,steps,aAngleMin,aAngleMax);
}

bool KnobInt(const char *label, int *p_value, int v_min, int v_max, float speed, const char *format, ImGuiKnobVariant variant, float size, ImGuiKnobFlags flags, int steps) {
Expand Down
8 changes: 7 additions & 1 deletion imgui-knobs.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#pragma once

#include <cstdlib>

#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui.h>

typedef int ImGuiKnobFlags;
Expand All @@ -10,6 +12,9 @@ enum ImGuiKnobFlags_ {
ImGuiKnobFlags_NoInput = 1 << 1,
ImGuiKnobFlags_ValueTooltip = 1 << 2,
ImGuiKnobFlags_DragHorizontal = 1 << 3,
ImGuiKnobFlags_RotateRelative = 1 << 4,
ImGuiKnobFlags_RotateAbsolute = 1 << 5,
ImGuiKnobFlags_WrapAround = 1 << 6,
};

typedef int ImGuiKnobVariant;
Expand Down Expand Up @@ -40,6 +45,7 @@ namespace ImGuiKnobs {
}
};

bool Knob(const char *label, float *p_value, float v_min, float v_max, float speed = 0, const char *format = NULL, ImGuiKnobVariant variant = ImGuiKnobVariant_Tick, float size = 0, ImGuiKnobFlags flags = 0, int steps = 10);
bool Knob(const char *label, float *p_value, float v_min, float v_max, float speed = 0, const char *format = NULL, ImGuiKnobVariant variant = ImGuiKnobVariant_Tick, float size = 0, ImGuiKnobFlags flags = 0, int steps = 10
,float aAngleMin=3.14159265358979323846f*0.75f,float aAngleMax=3.14159265358979323846f*2.25f);
bool KnobInt(const char *label, int *p_value, int v_min, int v_max, float speed = 0, const char *format = NULL, ImGuiKnobVariant variant = ImGuiKnobVariant_Tick, float size = 0, ImGuiKnobFlags flags = 0, int steps = 10);
}// namespace ImGuiKnobs