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