1 /**
2 * The MIT License (MIT)
3 *
4 * Copyright (c) 2019 Jean Pierre Cimalando
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy of
7 * this software and associated documentation files (the "Software"), to deal in
8 * the Software without restriction, including without limitation the rights to
9 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 * the Software, and to permit persons to whom the Software is furnished to do so,
11 * subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23
24 #include "Knob.h"
25 #include "CairoExtra.h"
26 #include "Cairo.hpp"
27 #include "Window.hpp"
28 #include <complex>
29
30 typedef std::complex<double> cdouble;
31
32 ///
Knob(Widget * group)33 Knob::Knob(Widget *group)
34 : CControl(group)
35 {
36 setSize(30, 30);
37 }
38
setValue(double value,NotifyMode notify)39 void Knob::setValue(double value, NotifyMode notify)
40 {
41 CControl::setValue(clampToBounds(value), notify);
42 }
43
setValueBounds(double v1,double v2)44 void Knob::setValueBounds(double v1, double v2)
45 {
46 fValueBound1 = v1;
47 fValueBound2 = v2;
48 setValue(getValue());
49 }
50
setLogarithmic(bool log)51 void Knob::setLogarithmic(bool log)
52 {
53 if (fIsLogarithmic == log)
54 return;
55
56 fIsLogarithmic = log;
57 repaint();
58 }
59
setNumSteps(unsigned numSteps)60 void Knob::setNumSteps(unsigned numSteps)
61 {
62 fNumSteps = numSteps;
63 }
64
onMouse(const MouseEvent & event)65 bool Knob::onMouse(const MouseEvent &event)
66 {
67 DGL::Size<uint> wsize = getSize();
68 DGL::Point<int> mpos = event.pos;
69
70 if (!fIsDragging && event.press && event.button == 1)
71 {
72 bool insideX = mpos.getX() >= 0 && (unsigned)mpos.getX() < wsize.getWidth();
73 bool insideY = mpos.getY() >= 0 && (unsigned)mpos.getY() < wsize.getHeight();
74
75 if (!insideX || !insideY)
76 return false;
77
78 fIsDragging = true;
79 beginChangeGesture();
80
81 return true;
82 }
83 else if (fIsDragging && !event.press && event.button == 1)
84 {
85 fIsDragging = false;
86 endChangeGesture();
87 return true;
88 }
89
90 return false;
91 }
92
onMotion(const MotionEvent & event)93 bool Knob::onMotion(const MotionEvent &event)
94 {
95 DGL::Size<uint> wsize = getSize();
96 DGL::Point<int> mpos = event.pos;
97
98 if (fIsDragging) {
99 double dx = mpos.getX() - 0.5 * getWidth();
100 double dy = mpos.getY() - 0.5 * getHeight();
101 if (dx * dx + dy * dy > 100.0) {
102 double angle = std::atan2(dx, -dy);
103 angle = std::max(angle, fAngleMin);
104 angle = std::min(angle, fAngleMax);
105 double fill = (angle - fAngleMin) / (fAngleMax - fAngleMin);
106 setValue(valueForRatio(fill));
107 }
108 return true;
109 }
110
111 return false;
112 }
113
onScroll(const ScrollEvent & event)114 bool Knob::onScroll(const ScrollEvent &event)
115 {
116 DGL::Size<uint> wsize = getSize();
117 DGL::Point<int> mpos = event.pos;
118
119 bool inside = mpos.getX() >= 0 && mpos.getY() >= 0 && (unsigned)mpos.getX() < wsize.getWidth() &&
120 (unsigned)mpos.getY() < wsize.getHeight();
121
122 if (inside) {
123 double amount = event.delta.getY() - event.delta.getX();
124
125 double fill = ratioForValue(getValue()) + amount / fNumSteps;
126 if (fill < 0) fill = 0;
127 if (fill > 1) fill = 1;
128
129 setValue(valueForRatio(fill));
130
131 return true;
132 }
133
134 return false;
135 }
136
onDisplay()137 void Knob::onDisplay()
138 {
139 cairo_t *cr = getParentWindow().getGraphicsContext().cairo;
140 int w = getWidth();
141 int h = getHeight();
142
143 double fill = ratioForValue(getValue());
144
145 cairo_save(cr);
146
147 double xc = 0.5 * w;
148 double yc = 0.5 * h;
149 double rad = 0.9 * ((xc < yc) ? xc : yc);
150 double trackw = 6.0;
151 double btnrad = 6.0;
152
153 double a1 = fAngleMin - M_PI / 2.0;
154 double a2 = fAngleMax - M_PI / 2.0;
155 double a = a1 + fill * (a2 - a1);
156
157 cairo_set_line_width(cr, trackw);
158 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
159
160 cairo_new_path(cr);
161 cairo_arc(cr, xc, yc, rad, a1, a2);
162 cairo_set_source_rgba32(cr, 0x8a8a8aff);
163 cairo_stroke(cr);
164
165 cairo_new_path(cr);
166 cairo_arc(cr, xc, yc, rad, a1, a);
167 cairo_set_source_rgba32(cr, 0xffffffff);
168 cairo_stroke(cr);
169
170 cdouble btncoord = std::polar(rad, a) + cdouble{xc, yc};
171 double btnx = btncoord.real();
172 double btny = btncoord.imag();
173
174 cairo_set_line_width(cr, 1.0);
175
176 cairo_new_path(cr);
177 cairo_arc(cr, btnx, btny, btnrad, 0.0, 2.0 * M_PI);
178 cairo_set_source_rgba32(cr, 0xffffffff);
179 cairo_fill_preserve(cr);
180 cairo_set_source_rgba32(cr, 0x8a8a8aff);
181 cairo_stroke(cr);
182
183 cairo_restore(cr);
184 }
185
clampToBounds(double value)186 double Knob::clampToBounds(double value)
187 {
188 double vmin = fValueBound1;
189 double vmax = fValueBound2;
190 if (vmin > vmax)
191 std::swap(vmin, vmax);
192
193 value = (value < vmin) ? vmin : value;
194 value = (value > vmax) ? vmax : value;
195 return value;
196 }
197
valueForRatio(double ratio) const198 double Knob::valueForRatio(double ratio) const
199 {
200 double v1 = fValueBound1;
201 double v2 = fValueBound2;
202 if (!fIsLogarithmic)
203 return v1 + ratio * (v2 - v1);
204 else
205 return v1 * pow(v2 / v1, ratio);
206 }
207
ratioForValue(double value) const208 double Knob::ratioForValue(double value) const
209 {
210 double v1 = fValueBound1;
211 double v2 = fValueBound2;
212 if (!fIsLogarithmic)
213 return (value - v1) / (v2 - v1);
214 else
215 return log(value / v1) / log(v2 / v1);
216 }
217