Skip to content

Commit 9bf98fe

Browse files
committed
Enhanced Knobs for gui, added strict numerical editing
1 parent b79421f commit 9bf98fe

39 files changed

+516
-143
lines changed

src/core/imgui-knobs.cpp

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
#include "imgui-knobs.h"
2+
3+
#include <cmath>
4+
#include <cstdlib>
5+
#include <imgui.h>
6+
#include <imgui_internal.h>
7+
8+
#define IMGUIKNOBS_PI 3.14159265358979323846f
9+
10+
namespace ImGuiKnobs {
11+
namespace detail {
12+
void draw_arc1(ImVec2 center, float radius, float start_angle, float end_angle, float thickness, ImColor color, int num_segments) {
13+
ImVec2 start = {
14+
center[0] + cosf(start_angle) * radius,
15+
center[1] + sinf(start_angle) * radius,
16+
};
17+
18+
ImVec2 end = {
19+
center[0] + cosf(end_angle) * radius,
20+
center[1] + sinf(end_angle) * radius,
21+
};
22+
23+
// Calculate bezier arc points
24+
auto ax = start[0] - center[0];
25+
auto ay = start[1] - center[1];
26+
auto bx = end[0] - center[0];
27+
auto by = end[1] - center[1];
28+
auto q1 = ax * ax + ay * ay;
29+
auto q2 = q1 + ax * bx + ay * by;
30+
auto k2 = (4.0f / 3.0f) * (sqrtf((2.0f * q1 * q2)) - q2) / (ax * by - ay * bx);
31+
auto arc1 = ImVec2{center[0] + ax - k2 * ay, center[1] + ay + k2 * ax};
32+
auto arc2 = ImVec2{center[0] + bx + k2 * by, center[1] + by - k2 * bx};
33+
34+
auto *draw_list = ImGui::GetWindowDrawList();
35+
36+
draw_list->AddBezierCurve(start, arc1, arc2, end, color, thickness, num_segments);
37+
}
38+
39+
void draw_arc(ImVec2 center, float radius, float start_angle, float end_angle, float thickness, ImColor color, int num_segments, int bezier_count) {
40+
// Overlap and angle of ends of bezier curves needs work, only looks good when not transperant
41+
auto overlap = thickness * radius * 0.00001f * IMGUIKNOBS_PI;
42+
auto delta = end_angle - start_angle;
43+
auto bez_step = 1.0f / bezier_count;
44+
auto mid_angle = start_angle + overlap;
45+
46+
for (auto i = 0; i < bezier_count - 1; i++) {
47+
auto mid_angle2 = delta * bez_step + mid_angle;
48+
draw_arc1(center, radius, mid_angle - overlap, mid_angle2 + overlap, thickness, color, num_segments);
49+
mid_angle = mid_angle2;
50+
}
51+
52+
draw_arc1(center, radius, mid_angle - overlap, end_angle, thickness, color, num_segments);
53+
}
54+
55+
template<typename DataType>
56+
struct knob {
57+
float radius;
58+
bool value_changed;
59+
ImVec2 center;
60+
bool is_active;
61+
bool is_hovered;
62+
float angle_min;
63+
float angle_max;
64+
float t;
65+
float angle;
66+
float angle_cos;
67+
float angle_sin;
68+
69+
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) {
70+
radius = _radius;
71+
t = ((float) *p_value - v_min) / (v_max - v_min);
72+
auto screen_pos = ImGui::GetCursorScreenPos();
73+
74+
// Handle dragging
75+
ImGui::InvisibleButton(_label, {radius * 2.0f, radius * 2.0f});
76+
auto gid = ImGui::GetID(_label);
77+
ImGuiSliderFlags drag_flags = 0;
78+
if (!(flags & ImGuiKnobFlags_DragHorizontal)) {
79+
drag_flags |= ImGuiSliderFlags_Vertical;
80+
}
81+
value_changed = ImGui::DragBehavior(gid, data_type, p_value, speed, &v_min, &v_max, format, 1, drag_flags);
82+
83+
angle_min = IMGUIKNOBS_PI * 0.75f;
84+
angle_max = IMGUIKNOBS_PI * 2.25f;
85+
center = {screen_pos[0] + radius, screen_pos[1] + radius};
86+
is_active = ImGui::IsItemActive();
87+
is_hovered = ImGui::IsItemHovered();
88+
angle = angle_min + (angle_max - angle_min) * t;
89+
angle_cos = cosf(angle);
90+
angle_sin = sinf(angle);
91+
}
92+
93+
void draw_dot(float size, float radius, float angle, color_set color, bool filled, int segments) {
94+
auto dot_size = size * this->radius;
95+
auto dot_radius = radius * this->radius;
96+
97+
ImGui::GetWindowDrawList()->AddCircleFilled(
98+
{center[0] + cosf(angle) * dot_radius, center[1] + sinf(angle) * dot_radius},
99+
dot_size,
100+
is_active ? color.active : (is_hovered ? color.hovered : color.base),
101+
segments);
102+
}
103+
104+
void draw_tick(float start, float end, float width, float angle, color_set color) {
105+
auto tick_start = start * radius;
106+
auto tick_end = end * radius;
107+
auto angle_cos = cosf(angle);
108+
auto angle_sin = sinf(angle);
109+
110+
ImGui::GetWindowDrawList()->AddLine(
111+
{center[0] + angle_cos * tick_end, center[1] + angle_sin * tick_end},
112+
{center[0] + angle_cos * tick_start, center[1] + angle_sin * tick_start},
113+
is_active ? color.active : (is_hovered ? color.hovered : color.base),
114+
width * radius);
115+
}
116+
117+
void draw_circle(float size, color_set color, bool filled, int segments) {
118+
auto circle_radius = size * radius;
119+
120+
ImGui::GetWindowDrawList()->AddCircleFilled(
121+
center,
122+
circle_radius,
123+
is_active ? color.active : (is_hovered ? color.hovered : color.base));
124+
}
125+
126+
void draw_arc(float radius, float size, float start_angle, float end_angle, color_set color, int segments, int bezier_count) {
127+
auto track_radius = radius * this->radius;
128+
auto track_size = size * this->radius * 0.5f + 0.0001f;
129+
130+
detail::draw_arc(
131+
center,
132+
track_radius,
133+
start_angle,
134+
end_angle,
135+
track_size,
136+
is_active ? color.active : (is_hovered ? color.hovered : color.base),
137+
segments,
138+
bezier_count);
139+
}
140+
};
141+
142+
template<typename DataType>
143+
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) {
144+
auto speed = _speed == 0 ? (v_max - v_min) / 250.f : _speed;
145+
ImGui::PushID(label);
146+
auto width = size == 0 ? ImGui::GetTextLineHeight() * 4.0f : size * ImGui::GetIO().FontGlobalScale;
147+
ImGui::PushItemWidth(width);
148+
149+
ImGui::BeginGroup();
150+
151+
// There's an issue with `SameLine` and Groups, see https://github.com/ocornut/imgui/issues/4190.
152+
// This is probably not the best solution, but seems to work for now
153+
ImGui::GetCurrentWindow()->DC.CurrLineTextBaseOffset = 0;
154+
155+
// Draw title
156+
if (!(flags & ImGuiKnobFlags_NoTitle)) {
157+
auto title_size = ImGui::CalcTextSize(label, NULL, false, width);
158+
159+
// Center title
160+
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (width - title_size[0]) * 0.5f);
161+
162+
ImGui::Text("%s", label);
163+
}
164+
165+
// Draw knob
166+
knob<DataType> k(label, data_type, p_value, v_min, v_max, speed, width * 0.5f, format, flags);
167+
168+
// Draw tooltip
169+
if (flags & ImGuiKnobFlags_ValueTooltip && (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) || ImGui::IsItemActive())) {
170+
ImGui::BeginTooltip();
171+
ImGui::Text(format, *p_value);
172+
ImGui::EndTooltip();
173+
}
174+
175+
// Draw input
176+
if (!(flags & ImGuiKnobFlags_NoInput)) {
177+
ImGuiSliderFlags drag_flags = 0;
178+
if (!(flags & ImGuiKnobFlags_DragHorizontal)) {
179+
drag_flags |= ImGuiSliderFlags_Vertical;
180+
}
181+
auto changed = ImGui::DragScalar("###knob_drag", data_type, p_value, speed, &v_min, &v_max, format, drag_flags);
182+
if (changed) {
183+
k.value_changed = true;
184+
}
185+
}
186+
187+
ImGui::EndGroup();
188+
ImGui::PopItemWidth();
189+
ImGui::PopID();
190+
191+
return k;
192+
}
193+
194+
color_set GetPrimaryColorSet() {
195+
auto *colors = ImGui::GetStyle().Colors;
196+
197+
return {colors[ImGuiCol_ButtonActive], colors[ImGuiCol_ButtonHovered], colors[ImGuiCol_ButtonHovered]};
198+
}
199+
200+
color_set GetSecondaryColorSet() {
201+
auto *colors = ImGui::GetStyle().Colors;
202+
auto active = ImVec4(
203+
colors[ImGuiCol_ButtonActive].x * 0.5f,
204+
colors[ImGuiCol_ButtonActive].y * 0.5f,
205+
colors[ImGuiCol_ButtonActive].z * 0.5f,
206+
colors[ImGuiCol_ButtonActive].w);
207+
208+
auto hovered = ImVec4(
209+
colors[ImGuiCol_ButtonHovered].x * 0.5f,
210+
colors[ImGuiCol_ButtonHovered].y * 0.5f,
211+
colors[ImGuiCol_ButtonHovered].z * 0.5f,
212+
colors[ImGuiCol_ButtonHovered].w);
213+
214+
return {active, hovered, hovered};
215+
}
216+
217+
color_set GetTrackColorSet() {
218+
auto *colors = ImGui::GetStyle().Colors;
219+
220+
return {colors[ImGuiCol_FrameBg], colors[ImGuiCol_FrameBg], colors[ImGuiCol_FrameBg]};
221+
}
222+
223+
color_set GetCustomColorSet() {
224+
225+
return {ImColor(255,255,120,255), ImColor(255,255,120,255), ImColor(255,255,120,255)};
226+
}
227+
}// namespace detail
228+
229+
230+
template<typename DataType>
231+
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) {
232+
auto knob = detail::knob_with_drag(label, data_type, p_value, v_min, v_max, speed, format, size, flags);
233+
234+
switch (variant) {
235+
case ImGuiKnobVariant_Tick: {
236+
knob.draw_circle(0.85f, detail::GetSecondaryColorSet(), true, 32);
237+
knob.draw_tick(0.5f, 0.85f, 0.08f, knob.angle, detail::GetPrimaryColorSet());
238+
break;
239+
}
240+
case ImGuiKnobVariant_Dot: {
241+
knob.draw_circle(0.85f, detail::GetSecondaryColorSet(), true, 32);
242+
knob.draw_dot(0.12f, 0.6f, knob.angle, detail::GetPrimaryColorSet(), true, 12);
243+
break;
244+
}
245+
246+
case ImGuiKnobVariant_Wiper: {
247+
knob.draw_circle(0.7f, detail::GetSecondaryColorSet(), true, 32);
248+
knob.draw_arc(0.8f, 0.41f, knob.angle_min, knob.angle_max, detail::GetTrackColorSet(), 16, 2);
249+
250+
if (knob.t > 0.01f) {
251+
knob.draw_arc(0.8f, 0.43f, knob.angle_min, knob.angle, detail::GetCustomColorSet(), 16, 2);
252+
}
253+
knob.draw_tick(0.5f, 0.88f, 0.16f, knob.angle, detail::GetCustomColorSet());
254+
break;
255+
}
256+
case ImGuiKnobVariant_WiperOnly: {
257+
knob.draw_arc(0.8f, 0.41f, knob.angle_min, knob.angle_max, detail::GetTrackColorSet(), 32, 2);
258+
259+
if (knob.t > 0.01) {
260+
knob.draw_arc(0.8f, 0.43f, knob.angle_min, knob.angle, detail::GetPrimaryColorSet(), 16, 2);
261+
}
262+
break;
263+
}
264+
case ImGuiKnobVariant_WiperDot: {
265+
knob.draw_circle(0.6f, detail::GetSecondaryColorSet(), true, 32);
266+
knob.draw_arc(0.85f, 0.41f, knob.angle_min, knob.angle_max, detail::GetTrackColorSet(), 16, 2);
267+
knob.draw_dot(0.1f, 0.85f, knob.angle, detail::GetPrimaryColorSet(), true, 12);
268+
break;
269+
}
270+
case ImGuiKnobVariant_Stepped: {
271+
for (auto n = 0.f; n < steps; n++) {
272+
auto a = n / (steps - 1);
273+
auto angle = knob.angle_min + (knob.angle_max - knob.angle_min) * a;
274+
knob.draw_tick(0.7f, 0.9f, 0.04f, angle, detail::GetPrimaryColorSet());
275+
}
276+
277+
knob.draw_circle(0.6f, detail::GetSecondaryColorSet(), true, 32);
278+
knob.draw_dot(0.12f, 0.4f, knob.angle, detail::GetPrimaryColorSet(), true, 12);
279+
break;
280+
}
281+
case ImGuiKnobVariant_Space: {
282+
knob.draw_circle(0.3f - knob.t * 0.1f, detail::GetSecondaryColorSet(), true, 16);
283+
284+
if (knob.t > 0.01f) {
285+
knob.draw_arc(0.4f, 0.15f, knob.angle_min - 1.0f, knob.angle - 1.0f, detail::GetPrimaryColorSet(), 16, 2);
286+
knob.draw_arc(0.6f, 0.15f, knob.angle_min + 1.0f, knob.angle + 1.0f, detail::GetPrimaryColorSet(), 16, 2);
287+
knob.draw_arc(0.8f, 0.15f, knob.angle_min + 3.0f, knob.angle + 3.0f, detail::GetPrimaryColorSet(), 16, 2);
288+
}
289+
break;
290+
}
291+
}
292+
293+
return knob.value_changed;
294+
}
295+
296+
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) {
297+
const char *_format = format == NULL ? "%.3f" : format;
298+
return BaseKnob(label, ImGuiDataType_Float, p_value, v_min, v_max, speed, _format, variant, size, flags, steps);
299+
}
300+
301+
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) {
302+
const char *_format = format == NULL ? "%i" : format;
303+
return BaseKnob(label, ImGuiDataType_S32, p_value, v_min, v_max, speed, _format, variant, size, flags, steps);
304+
}
305+
306+
}// namespace ImGuiKnobs

src/core/imgui-knobs.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#pragma once
2+
3+
#include <cstdlib>
4+
#include <imgui.h>
5+
6+
typedef int ImGuiKnobFlags;
7+
8+
enum ImGuiKnobFlags_ {
9+
ImGuiKnobFlags_NoTitle = 1 << 0,
10+
ImGuiKnobFlags_NoInput = 1 << 1,
11+
ImGuiKnobFlags_ValueTooltip = 1 << 2,
12+
ImGuiKnobFlags_DragHorizontal = 1 << 3,
13+
};
14+
15+
typedef int ImGuiKnobVariant;
16+
17+
enum ImGuiKnobVariant_ {
18+
ImGuiKnobVariant_Tick = 1 << 0,
19+
ImGuiKnobVariant_Dot = 1 << 1,
20+
ImGuiKnobVariant_Wiper = 1 << 2,
21+
ImGuiKnobVariant_WiperOnly = 1 << 3,
22+
ImGuiKnobVariant_WiperDot = 1 << 4,
23+
ImGuiKnobVariant_Stepped = 1 << 5,
24+
ImGuiKnobVariant_Space = 1 << 6,
25+
};
26+
27+
namespace ImGuiKnobs {
28+
29+
struct color_set {
30+
ImColor base;
31+
ImColor hovered;
32+
ImColor active;
33+
34+
color_set(ImColor base, ImColor hovered, ImColor active) : base(base), hovered(hovered), active(active) {}
35+
36+
color_set(ImColor color) {
37+
base = color;
38+
hovered = color;
39+
active = color;
40+
}
41+
};
42+
43+
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);
44+
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);
45+
}// namespace ImGuiKnobs

src/objects/audio_analysis/AudioAnalyzer.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ AudioAnalyzer::AudioAnalyzer() : PatchObject("audio analyzer"){
6767
isLoaded = false;
6868

6969
this->width *= 1.3f;
70-
this->height *= 1.8f;
70+
this->height *= 2.0f;
7171
}
7272

7373
//--------------------------------------------------------------
@@ -208,11 +208,13 @@ void AudioAnalyzer::drawObjectNodeGui( ImGuiEx::NodeCanvas& _nodeCanvas ){
208208
ImGui::Spacing();
209209
ImGui::Spacing();
210210

211-
if(ImGuiEx::KnobFloat(_nodeCanvas.getNodeDrawList(), (ImGui::GetWindowSize().x-(46*scaleFactor))/7, IM_COL32(255,255,120,255), "level", &audioInputLevel, 0.0f, 1.0f, 100.0f)){
211+
ImGui::Dummy(ImVec2(0,4*scaleFactor));
212+
if (ImGuiKnobs::Knob("level", &audioInputLevel, 0.0f, 1.0f, 0.01f, "%.2f", ImGuiKnobVariant_Wiper)) {
212213
this->setCustomVar(static_cast<float>(audioInputLevel),"INPUT_LEVEL");
213214
}
215+
214216
ImGui::SameLine();
215-
if(ImGuiEx::KnobFloat(_nodeCanvas.getNodeDrawList(), (ImGui::GetWindowSize().x-(46*scaleFactor))/7, IM_COL32(255,255,120,255), "smooth", &smoothingValue, 0.0f, 1.0f, 100.0f)){
217+
if (ImGuiKnobs::Knob("smooth", &smoothingValue, 0.0f, 1.0f, 0.01f, "%.2f", ImGuiKnobVariant_Wiper)) {
216218
this->setCustomVar(static_cast<float>(smoothingValue),"SMOOTHING");
217219
}
218220

src/objects/audio_analysis/AudioAnalyzer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
#include "ofxBTrack.h"
4141

4242
#include "imgui_plot.h"
43-
#include "imgui_controls.h"
43+
#include "imgui-knobs.h"
4444

4545

4646
class AudioAnalyzer : public PatchObject {

0 commit comments

Comments
 (0)