diff --git a/README.md b/README.md index 713b2d7..9819224 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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]) ``` @@ -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. diff --git a/imgui-knobs.cpp b/imgui-knobs.cpp index 9f59a5c..c257ea9 100644 --- a/imgui-knobs.cpp +++ b/imgui-knobs.cpp @@ -2,13 +2,39 @@ #include #include +#define IMGUI_DEFINE_MATH_OPERATORS #include #include #define IMGUIKNOBS_PI 3.14159265358979323846f + + namespace ImGuiKnobs { namespace detail { + + + // Re-Maps clamped + template + 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, @@ -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) { @@ -53,7 +79,8 @@ namespace ImGuiKnobs { } template - struct knob { + struct knob + { float radius; bool value_changed; ImVec2 center; @@ -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(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(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(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}; + 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; @@ -140,7 +299,8 @@ namespace ImGuiKnobs { }; template - knob 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 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; @@ -163,7 +323,8 @@ namespace ImGuiKnobs { } // Draw knob - knob k(label, data_type, p_value, v_min, v_max, speed, width * 0.5f, format, flags); + knob 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())) { @@ -223,8 +384,9 @@ namespace ImGuiKnobs { template - 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: { @@ -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) { diff --git a/imgui-knobs.h b/imgui-knobs.h index 9587634..5a5bcdf 100644 --- a/imgui-knobs.h +++ b/imgui-knobs.h @@ -1,6 +1,8 @@ #pragma once #include + +#define IMGUI_DEFINE_MATH_OPERATORS #include typedef int ImGuiKnobFlags; @@ -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; @@ -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