1 /*
2  * DISTRHO Plugin Framework (DPF)
3  * Copyright (C) 2012-2019 Filipe Coelho <falktx@falktx.com>
4  *
5  * Permission to use, copy, modify, and/or distribute this software for any purpose with
6  * or without fee is hereby granted, provided that the above copyright notice and this
7  * permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
10  * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
11  * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
12  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
13  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
14  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 #include "../Image.hpp"
18 #include "Common.hpp"
19 #include "WidgetPrivateData.hpp"
20 
21 // FIXME make this code more generic and move GL specific bits to OpenGL.cpp
22 #include "../OpenGL.hpp"
23 
24 START_NAMESPACE_DGL
25 
26 // -----------------------------------------------------------------------
27 
28 #ifndef DISTRHO_OS_HAIKU
ImageAboutWindow(Window & parent,const Image & image)29 ImageAboutWindow::ImageAboutWindow(Window& parent, const Image& image)
30     : Window(parent.getApp(), parent),
31       Widget((Window&)*this),
32       fImgBackground(image)
33 {
34     Window::setResizable(false);
35     Window::setSize(image.getSize());
36     Window::setTitle("About");
37 }
38 
ImageAboutWindow(Widget * widget,const Image & image)39 ImageAboutWindow::ImageAboutWindow(Widget* widget, const Image& image)
40     : Window(widget->getParentApp(), widget->getParentWindow()),
41       Widget((Window&)*this),
42       fImgBackground(image)
43 {
44     Window::setResizable(false);
45     Window::setSize(image.getSize());
46     Window::setTitle("About");
47 }
48 #else
49 ImageAboutWindow::ImageAboutWindow(Window& parent, const Image& image) : fImgBackground(image) {}
50 ImageAboutWindow::ImageAboutWindow(Widget* widget, const Image& image) : fImgBackground(image) {}
51 #endif
52 
setImage(const Image & image)53 void ImageAboutWindow::setImage(const Image& image)
54 {
55     if (fImgBackground == image)
56         return;
57 
58     fImgBackground = image;
59 #ifndef DISTRHO_OS_HAIKU
60     Window::setSize(image.getSize());
61 #endif
62 }
63 
64 #ifndef DISTRHO_OS_HAIKU
onDisplay()65 void ImageAboutWindow::onDisplay()
66 {
67     fImgBackground.draw();
68 }
69 
onKeyboard(const KeyboardEvent & ev)70 bool ImageAboutWindow::onKeyboard(const KeyboardEvent& ev)
71 {
72     if (ev.press && ev.key == kCharEscape)
73     {
74         Window::close();
75         return true;
76     }
77 
78     return false;
79 }
80 
onMouse(const MouseEvent & ev)81 bool ImageAboutWindow::onMouse(const MouseEvent& ev)
82 {
83     if (ev.press)
84     {
85         Window::close();
86         return true;
87     }
88 
89     return false;
90 }
91 
onReshape(uint width,uint height)92 void ImageAboutWindow::onReshape(uint width, uint height)
93 {
94     Widget::setSize(width, height);
95     Window::onReshape(width, height);
96 }
97 #endif
98 
99 // -----------------------------------------------------------------------
100 
101 struct ImageButton::PrivateData {
102     ButtonImpl impl;
103     Image imageNormal;
104     Image imageHover;
105     Image imageDown;
106 
PrivateDataImageButton::PrivateData107     PrivateData(Widget* const s, const Image& normal, const Image& hover, const Image& down)
108         : impl(s),
109           imageNormal(normal),
110           imageHover(hover),
111           imageDown(down) {}
112 
113     DISTRHO_DECLARE_NON_COPY_STRUCT(PrivateData)
114 };
115 
116 // -----------------------------------------------------------------------
117 
ImageButton(Window & parent,const Image & image)118 ImageButton::ImageButton(Window& parent, const Image& image)
119     : Widget(parent),
120       pData(new PrivateData(this, image, image, image))
121 {
122     setSize(image.getSize());
123 }
124 
ImageButton(Window & parent,const Image & imageNormal,const Image & imageDown)125 ImageButton::ImageButton(Window& parent, const Image& imageNormal, const Image& imageDown)
126     : Widget(parent),
127       pData(new PrivateData(this, imageNormal, imageNormal, imageDown))
128 {
129     DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize());
130 
131     setSize(imageNormal.getSize());
132 }
133 
ImageButton(Window & parent,const Image & imageNormal,const Image & imageHover,const Image & imageDown)134 ImageButton::ImageButton(Window& parent, const Image& imageNormal, const Image& imageHover, const Image& imageDown)
135     : Widget(parent),
136       pData(new PrivateData(this, imageNormal, imageHover, imageDown))
137 {
138     DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageHover.getSize() && imageHover.getSize() == imageDown.getSize());
139 
140     setSize(imageNormal.getSize());
141 }
142 
ImageButton(Widget * widget,const Image & image)143 ImageButton::ImageButton(Widget* widget, const Image& image)
144     : Widget(widget->getParentWindow()),
145       pData(new PrivateData(this, image, image, image))
146 {
147     setSize(image.getSize());
148 }
149 
ImageButton(Widget * widget,const Image & imageNormal,const Image & imageDown)150 ImageButton::ImageButton(Widget* widget, const Image& imageNormal, const Image& imageDown)
151     : Widget(widget->getParentWindow()),
152       pData(new PrivateData(this, imageNormal, imageNormal, imageDown))
153 {
154     DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize());
155 
156     setSize(imageNormal.getSize());
157 }
158 
ImageButton(Widget * widget,const Image & imageNormal,const Image & imageHover,const Image & imageDown)159 ImageButton::ImageButton(Widget* widget, const Image& imageNormal, const Image& imageHover, const Image& imageDown)
160     : Widget(widget->getParentWindow()),
161       pData(new PrivateData(this, imageNormal, imageHover, imageDown))
162 {
163     DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageHover.getSize() && imageHover.getSize() == imageDown.getSize());
164 
165     setSize(imageNormal.getSize());
166 }
167 
~ImageButton()168 ImageButton::~ImageButton()
169 {
170     delete pData;
171 }
172 
setCallback(Callback * callback)173 void ImageButton::setCallback(Callback* callback) noexcept
174 {
175     pData->impl.callback_img = callback;
176 }
177 
onDisplay()178 void ImageButton::onDisplay()
179 {
180     switch (pData->impl.state)
181     {
182     case ButtonImpl::kStateDown:
183         pData->imageDown.draw();
184         break;
185     case ButtonImpl::kStateHover:
186         pData->imageHover.draw();
187         break;
188     default:
189         pData->imageNormal.draw();
190         break;
191     }
192 }
193 
onMouse(const MouseEvent & ev)194 bool ImageButton::onMouse(const MouseEvent& ev)
195 {
196     return pData->impl.onMouse(ev);
197 }
198 
onMotion(const MotionEvent & ev)199 bool ImageButton::onMotion(const MotionEvent& ev)
200 {
201     return pData->impl.onMotion(ev);
202 }
203 
204 // -----------------------------------------------------------------------
205 
ImageKnob(Window & parent,const Image & image,Orientation orientation)206 ImageKnob::ImageKnob(Window& parent, const Image& image, Orientation orientation) noexcept
207     : Widget(parent),
208       fImage(image),
209       fMinimum(0.0f),
210       fMaximum(1.0f),
211       fStep(0.0f),
212       fValue(0.5f),
213       fValueDef(fValue),
214       fValueTmp(fValue),
215       fUsingDefault(false),
216       fUsingLog(false),
217       fOrientation(orientation),
218       fRotationAngle(0),
219       fDragging(false),
220       fLastX(0),
221       fLastY(0),
222       fCallback(nullptr),
223       fIsImgVertical(image.getHeight() > image.getWidth()),
224       fImgLayerWidth(fIsImgVertical ? image.getWidth() : image.getHeight()),
225       fImgLayerHeight(fImgLayerWidth),
226       fImgLayerCount(fIsImgVertical ? image.getHeight()/fImgLayerHeight : image.getWidth()/fImgLayerWidth),
227       fIsReady(false),
228       fTextureId(0)
229 {
230     glGenTextures(1, &fTextureId);
231     setSize(fImgLayerWidth, fImgLayerHeight);
232 }
233 
ImageKnob(Widget * widget,const Image & image,Orientation orientation)234 ImageKnob::ImageKnob(Widget* widget, const Image& image, Orientation orientation) noexcept
235     : Widget(widget->getParentWindow()),
236       fImage(image),
237       fMinimum(0.0f),
238       fMaximum(1.0f),
239       fStep(0.0f),
240       fValue(0.5f),
241       fValueDef(fValue),
242       fValueTmp(fValue),
243       fUsingDefault(false),
244       fUsingLog(false),
245       fOrientation(orientation),
246       fRotationAngle(0),
247       fDragging(false),
248       fLastX(0),
249       fLastY(0),
250       fCallback(nullptr),
251       fIsImgVertical(image.getHeight() > image.getWidth()),
252       fImgLayerWidth(fIsImgVertical ? image.getWidth() : image.getHeight()),
253       fImgLayerHeight(fImgLayerWidth),
254       fImgLayerCount(fIsImgVertical ? image.getHeight()/fImgLayerHeight : image.getWidth()/fImgLayerWidth),
255       fIsReady(false),
256       fTextureId(0)
257 {
258     glGenTextures(1, &fTextureId);
259     setSize(fImgLayerWidth, fImgLayerHeight);
260 }
261 
ImageKnob(const ImageKnob & imageKnob)262 ImageKnob::ImageKnob(const ImageKnob& imageKnob)
263     : Widget(imageKnob.getParentWindow()),
264       fImage(imageKnob.fImage),
265       fMinimum(imageKnob.fMinimum),
266       fMaximum(imageKnob.fMaximum),
267       fStep(imageKnob.fStep),
268       fValue(imageKnob.fValue),
269       fValueDef(imageKnob.fValueDef),
270       fValueTmp(fValue),
271       fUsingDefault(imageKnob.fUsingDefault),
272       fUsingLog(imageKnob.fUsingLog),
273       fOrientation(imageKnob.fOrientation),
274       fRotationAngle(imageKnob.fRotationAngle),
275       fDragging(false),
276       fLastX(0),
277       fLastY(0),
278       fCallback(imageKnob.fCallback),
279       fIsImgVertical(imageKnob.fIsImgVertical),
280       fImgLayerWidth(imageKnob.fImgLayerWidth),
281       fImgLayerHeight(imageKnob.fImgLayerHeight),
282       fImgLayerCount(imageKnob.fImgLayerCount),
283       fIsReady(false),
284       fTextureId(0)
285 {
286     glGenTextures(1, &fTextureId);
287     setSize(fImgLayerWidth, fImgLayerHeight);
288 }
289 
operator =(const ImageKnob & imageKnob)290 ImageKnob& ImageKnob::operator=(const ImageKnob& imageKnob)
291 {
292     fImage    = imageKnob.fImage;
293     fMinimum  = imageKnob.fMinimum;
294     fMaximum  = imageKnob.fMaximum;
295     fStep     = imageKnob.fStep;
296     fValue    = imageKnob.fValue;
297     fValueDef = imageKnob.fValueDef;
298     fValueTmp = fValue;
299     fUsingDefault  = imageKnob.fUsingDefault;
300     fUsingLog      = imageKnob.fUsingLog;
301     fOrientation   = imageKnob.fOrientation;
302     fRotationAngle = imageKnob.fRotationAngle;
303     fDragging = false;
304     fLastX    = 0;
305     fLastY    = 0;
306     fCallback = imageKnob.fCallback;
307     fIsImgVertical  = imageKnob.fIsImgVertical;
308     fImgLayerWidth  = imageKnob.fImgLayerWidth;
309     fImgLayerHeight = imageKnob.fImgLayerHeight;
310     fImgLayerCount  = imageKnob.fImgLayerCount;
311     fIsReady  = false;
312 
313     if (fTextureId != 0)
314     {
315         glDeleteTextures(1, &fTextureId);
316         fTextureId = 0;
317     }
318 
319     glGenTextures(1, &fTextureId);
320     setSize(fImgLayerWidth, fImgLayerHeight);
321 
322     return *this;
323 }
324 
~ImageKnob()325 ImageKnob::~ImageKnob()
326 {
327     if (fTextureId != 0)
328     {
329         glDeleteTextures(1, &fTextureId);
330         fTextureId = 0;
331     }
332 }
333 
getValue() const334 float ImageKnob::getValue() const noexcept
335 {
336     return fValue;
337 }
338 
339 // NOTE: value is assumed to be scaled if using log
setDefault(float value)340 void ImageKnob::setDefault(float value) noexcept
341 {
342     fValueDef = value;
343     fUsingDefault = true;
344 }
345 
setRange(float min,float max)346 void ImageKnob::setRange(float min, float max) noexcept
347 {
348     DISTRHO_SAFE_ASSERT_RETURN(max > min,);
349 
350     if (fValue < min)
351     {
352         fValue = min;
353         repaint();
354 
355         if (fCallback != nullptr)
356         {
357             try {
358                 fCallback->imageKnobValueChanged(this, fValue);
359             } DISTRHO_SAFE_EXCEPTION("ImageKnob::setRange < min");
360         }
361     }
362     else if (fValue > max)
363     {
364         fValue = max;
365         repaint();
366 
367         if (fCallback != nullptr)
368         {
369             try {
370                 fCallback->imageKnobValueChanged(this, fValue);
371             } DISTRHO_SAFE_EXCEPTION("ImageKnob::setRange > max");
372         }
373     }
374 
375     fMinimum = min;
376     fMaximum = max;
377 }
378 
setStep(float step)379 void ImageKnob::setStep(float step) noexcept
380 {
381     fStep = step;
382 }
383 
384 // NOTE: value is assumed to be scaled if using log
setValue(float value,bool sendCallback)385 void ImageKnob::setValue(float value, bool sendCallback) noexcept
386 {
387     if (d_isEqual(fValue, value))
388         return;
389 
390     fValue = value;
391 
392     if (d_isZero(fStep))
393         fValueTmp = value;
394 
395     if (fRotationAngle == 0)
396         fIsReady = false;
397 
398     repaint();
399 
400     if (sendCallback && fCallback != nullptr)
401     {
402         try {
403             fCallback->imageKnobValueChanged(this, fValue);
404         } DISTRHO_SAFE_EXCEPTION("ImageKnob::setValue");
405     }
406 }
407 
setUsingLogScale(bool yesNo)408 void ImageKnob::setUsingLogScale(bool yesNo) noexcept
409 {
410     fUsingLog = yesNo;
411 }
412 
setCallback(Callback * callback)413 void ImageKnob::setCallback(Callback* callback) noexcept
414 {
415     fCallback = callback;
416 }
417 
setOrientation(Orientation orientation)418 void ImageKnob::setOrientation(Orientation orientation) noexcept
419 {
420     if (fOrientation == orientation)
421         return;
422 
423     fOrientation = orientation;
424 }
425 
setRotationAngle(int angle)426 void ImageKnob::setRotationAngle(int angle)
427 {
428     if (fRotationAngle == angle)
429         return;
430 
431     fRotationAngle = angle;
432     fIsReady = false;
433 }
434 
setImageLayerCount(uint count)435 void ImageKnob::setImageLayerCount(uint count) noexcept
436 {
437     DISTRHO_SAFE_ASSERT_RETURN(count > 1,);
438 
439     fImgLayerCount = count;
440 
441     if (fIsImgVertical)
442         fImgLayerHeight = fImage.getHeight()/count;
443     else
444         fImgLayerWidth = fImage.getWidth()/count;
445 
446     setSize(fImgLayerWidth, fImgLayerHeight);
447 }
448 
onDisplay()449 void ImageKnob::onDisplay()
450 {
451     const float normValue = ((fUsingLog ? _invlogscale(fValue) : fValue) - fMinimum) / (fMaximum - fMinimum);
452 
453     glEnable(GL_TEXTURE_2D);
454     glBindTexture(GL_TEXTURE_2D, fTextureId);
455 
456     if (! fIsReady)
457     {
458         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
459         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
460         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
461         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
462 
463         static const float trans[] = { 0.0f, 0.0f, 0.0f, 0.0f };
464         glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, trans);
465 
466         glPixelStorei(GL_PACK_ALIGNMENT, 1);
467         glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
468 
469         uint imageDataOffset = 0;
470 
471         if (fRotationAngle == 0)
472         {
473             DISTRHO_SAFE_ASSERT_RETURN(fImgLayerCount > 0,);
474             DISTRHO_SAFE_ASSERT_RETURN(normValue >= 0.0f,);
475 
476             const uint& v1(fIsImgVertical ? fImgLayerWidth : fImgLayerHeight);
477             const uint& v2(fIsImgVertical ? fImgLayerHeight : fImgLayerWidth);
478 
479             const uint layerDataSize   = v1 * v2 * ((fImage.getFormat() == GL_BGRA || fImage.getFormat() == GL_RGBA) ? 4 : 3);
480             /*      */ imageDataOffset = layerDataSize * uint(normValue * float(fImgLayerCount-1));
481         }
482 
483         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
484                      static_cast<GLsizei>(getWidth()), static_cast<GLsizei>(getHeight()), 0,
485                      fImage.getFormat(), fImage.getType(), fImage.getRawData() + imageDataOffset);
486 
487         fIsReady = true;
488     }
489 
490     const int w = static_cast<int>(getWidth());
491     const int h = static_cast<int>(getHeight());
492 
493     if (fRotationAngle != 0)
494     {
495         glPushMatrix();
496 
497         const int w2 = w/2;
498         const int h2 = h/2;
499 
500         glTranslatef(static_cast<float>(w2), static_cast<float>(h2), 0.0f);
501         glRotatef(normValue*static_cast<float>(fRotationAngle), 0.0f, 0.0f, 1.0f);
502 
503         Rectangle<int>(-w2, -h2, w, h).draw();
504 
505         glPopMatrix();
506     }
507     else
508     {
509         Rectangle<int>(0, 0, w, h).draw();
510     }
511 
512     glBindTexture(GL_TEXTURE_2D, 0);
513     glDisable(GL_TEXTURE_2D);
514 }
515 
onMouse(const MouseEvent & ev)516 bool ImageKnob::onMouse(const MouseEvent& ev)
517 {
518     if (ev.button != 1)
519         return false;
520 
521     if (ev.press)
522     {
523         if (! contains(ev.pos))
524             return false;
525 
526         if ((ev.mod & kModifierShift) != 0 && fUsingDefault)
527         {
528             setValue(fValueDef, true);
529             fValueTmp = fValue;
530             return true;
531         }
532 
533         fDragging = true;
534         fLastX = ev.pos.getX();
535         fLastY = ev.pos.getY();
536 
537         if (fCallback != nullptr)
538             fCallback->imageKnobDragStarted(this);
539 
540         return true;
541     }
542     else if (fDragging)
543     {
544         if (fCallback != nullptr)
545             fCallback->imageKnobDragFinished(this);
546 
547         fDragging = false;
548         return true;
549     }
550 
551     return false;
552 }
553 
onMotion(const MotionEvent & ev)554 bool ImageKnob::onMotion(const MotionEvent& ev)
555 {
556     if (! fDragging)
557         return false;
558 
559     bool doVal = false;
560     float d, value = 0.0f;
561 
562     if (fOrientation == ImageKnob::Horizontal)
563     {
564         if (const int movX = ev.pos.getX() - fLastX)
565         {
566             d     = (ev.mod & kModifierControl) ? 2000.0f : 200.0f;
567             value = (fUsingLog ? _invlogscale(fValueTmp) : fValueTmp) + (float(fMaximum - fMinimum) / d * float(movX));
568             doVal = true;
569         }
570     }
571     else if (fOrientation == ImageKnob::Vertical)
572     {
573         if (const int movY = fLastY - ev.pos.getY())
574         {
575             d     = (ev.mod & kModifierControl) ? 2000.0f : 200.0f;
576             value = (fUsingLog ? _invlogscale(fValueTmp) : fValueTmp) + (float(fMaximum - fMinimum) / d * float(movY));
577             doVal = true;
578         }
579     }
580 
581     if (! doVal)
582         return false;
583 
584     if (fUsingLog)
585         value = _logscale(value);
586 
587     if (value < fMinimum)
588     {
589         fValueTmp = value = fMinimum;
590     }
591     else if (value > fMaximum)
592     {
593         fValueTmp = value = fMaximum;
594     }
595     else if (d_isNotZero(fStep))
596     {
597         fValueTmp = value;
598         const float rest = std::fmod(value, fStep);
599         value = value - rest + (rest > fStep/2.0f ? fStep : 0.0f);
600     }
601 
602     setValue(value, true);
603 
604     fLastX = ev.pos.getX();
605     fLastY = ev.pos.getY();
606 
607     return true;
608 }
609 
onScroll(const ScrollEvent & ev)610 bool ImageKnob::onScroll(const ScrollEvent& ev)
611 {
612     if (! contains(ev.pos))
613         return false;
614 
615     const float dir   = (ev.delta.getY() > 0.f) ? 1.f : -1.f;
616     const float d     = (ev.mod & kModifierControl) ? 2000.0f : 200.0f;
617     float       value = (fUsingLog ? _invlogscale(fValueTmp) : fValueTmp) + (float(fMaximum - fMinimum) / d * 10.f * dir);
618 
619     if (fUsingLog)
620         value = _logscale(value);
621 
622     if (value < fMinimum)
623     {
624         fValueTmp = value = fMinimum;
625     }
626     else if (value > fMaximum)
627     {
628         fValueTmp = value = fMaximum;
629     }
630     else if (d_isNotZero(fStep))
631     {
632         fValueTmp = value;
633         const float rest = std::fmod(value, fStep);
634         value = value - rest + (rest > fStep/2.0f ? fStep : 0.0f);
635     }
636 
637     setValue(value, true);
638     return true;
639 }
640 
641 // -----------------------------------------------------------------------
642 
_logscale(float value) const643 float ImageKnob::_logscale(float value) const
644 {
645     const float b = std::log(fMaximum/fMinimum)/(fMaximum-fMinimum);
646     const float a = fMaximum/std::exp(fMaximum*b);
647     return a * std::exp(b*value);
648 }
649 
_invlogscale(float value) const650 float ImageKnob::_invlogscale(float value) const
651 {
652     const float b = std::log(fMaximum/fMinimum)/(fMaximum-fMinimum);
653     const float a = fMaximum/std::exp(fMaximum*b);
654     return std::log(value/a)/b;
655 }
656 
657 // -----------------------------------------------------------------------
658 
ImageSlider(Window & parent,const Image & image)659 ImageSlider::ImageSlider(Window& parent, const Image& image) noexcept
660     : Widget(parent),
661       fImage(image),
662       fMinimum(0.0f),
663       fMaximum(1.0f),
664       fStep(0.0f),
665       fValue(0.5f),
666       fValueDef(fValue),
667       fValueTmp(fValue),
668       fUsingDefault(false),
669       fDragging(false),
670       fInverted(false),
671       fValueIsSet(false),
672       fStartedX(0),
673       fStartedY(0),
674       fCallback(nullptr),
675       fStartPos(),
676       fEndPos(),
677       fSliderArea()
678 {
679     pData->needsFullViewport = true;
680 }
681 
ImageSlider(Widget * widget,const Image & image)682 ImageSlider::ImageSlider(Widget* widget, const Image& image) noexcept
683     : Widget(widget->getParentWindow()),
684       fImage(image),
685       fMinimum(0.0f),
686       fMaximum(1.0f),
687       fStep(0.0f),
688       fValue(0.5f),
689       fValueDef(fValue),
690       fValueTmp(fValue),
691       fUsingDefault(false),
692       fDragging(false),
693       fInverted(false),
694       fValueIsSet(false),
695       fStartedX(0),
696       fStartedY(0),
697       fCallback(nullptr),
698       fStartPos(),
699       fEndPos(),
700       fSliderArea()
701 {
702     pData->needsFullViewport = true;
703 }
704 
getValue() const705 float ImageSlider::getValue() const noexcept
706 {
707     return fValue;
708 }
709 
setValue(float value,bool sendCallback)710 void ImageSlider::setValue(float value, bool sendCallback) noexcept
711 {
712     if (! fValueIsSet)
713         fValueIsSet = true;
714 
715     if (d_isEqual(fValue, value))
716         return;
717 
718     fValue = value;
719 
720     if (d_isZero(fStep))
721         fValueTmp = value;
722 
723     repaint();
724 
725     if (sendCallback && fCallback != nullptr)
726     {
727         try {
728             fCallback->imageSliderValueChanged(this, fValue);
729         } DISTRHO_SAFE_EXCEPTION("ImageSlider::setValue");
730     }
731 }
732 
setStartPos(const Point<int> & startPos)733 void ImageSlider::setStartPos(const Point<int>& startPos) noexcept
734 {
735     fStartPos = startPos;
736     _recheckArea();
737 }
738 
setStartPos(int x,int y)739 void ImageSlider::setStartPos(int x, int y) noexcept
740 {
741     setStartPos(Point<int>(x, y));
742 }
743 
setEndPos(const Point<int> & endPos)744 void ImageSlider::setEndPos(const Point<int>& endPos) noexcept
745 {
746     fEndPos = endPos;
747     _recheckArea();
748 }
749 
setEndPos(int x,int y)750 void ImageSlider::setEndPos(int x, int y) noexcept
751 {
752     setEndPos(Point<int>(x, y));
753 }
754 
setInverted(bool inverted)755 void ImageSlider::setInverted(bool inverted) noexcept
756 {
757     if (fInverted == inverted)
758         return;
759 
760     fInverted = inverted;
761     repaint();
762 }
763 
setDefault(float value)764 void ImageSlider::setDefault(float value) noexcept
765 {
766     fValueDef = value;
767     fUsingDefault = true;
768 }
769 
setRange(float min,float max)770 void ImageSlider::setRange(float min, float max) noexcept
771 {
772     fMinimum = min;
773     fMaximum = max;
774 
775     if (fValue < min)
776     {
777         fValue = min;
778         repaint();
779 
780         if (fCallback != nullptr && fValueIsSet)
781         {
782             try {
783                 fCallback->imageSliderValueChanged(this, fValue);
784             } DISTRHO_SAFE_EXCEPTION("ImageSlider::setRange < min");
785         }
786     }
787     else if (fValue > max)
788     {
789         fValue = max;
790         repaint();
791 
792         if (fCallback != nullptr && fValueIsSet)
793         {
794             try {
795                 fCallback->imageSliderValueChanged(this, fValue);
796             } DISTRHO_SAFE_EXCEPTION("ImageSlider::setRange > max");
797         }
798     }
799 }
800 
setStep(float step)801 void ImageSlider::setStep(float step) noexcept
802 {
803     fStep = step;
804 }
805 
setCallback(Callback * callback)806 void ImageSlider::setCallback(Callback* callback) noexcept
807 {
808     fCallback = callback;
809 }
810 
onDisplay()811 void ImageSlider::onDisplay()
812 {
813 #if 0 // DEBUG, paints slider area
814     glColor3f(0.4f, 0.5f, 0.1f);
815     glRecti(fSliderArea.getX(), fSliderArea.getY(), fSliderArea.getX()+fSliderArea.getWidth(), fSliderArea.getY()+fSliderArea.getHeight());
816     glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
817 #endif
818 
819     const float normValue = (fValue - fMinimum) / (fMaximum - fMinimum);
820 
821     int x, y;
822 
823     if (fStartPos.getY() == fEndPos.getY())
824     {
825         // horizontal
826         if (fInverted)
827             x = fEndPos.getX() - static_cast<int>(normValue*static_cast<float>(fEndPos.getX()-fStartPos.getX()));
828         else
829             x = fStartPos.getX() + static_cast<int>(normValue*static_cast<float>(fEndPos.getX()-fStartPos.getX()));
830 
831         y = fStartPos.getY();
832     }
833     else
834     {
835         // vertical
836         x = fStartPos.getX();
837 
838         if (fInverted)
839             y = fEndPos.getY() - static_cast<int>(normValue*static_cast<float>(fEndPos.getY()-fStartPos.getY()));
840         else
841             y = fStartPos.getY() + static_cast<int>(normValue*static_cast<float>(fEndPos.getY()-fStartPos.getY()));
842     }
843 
844     fImage.drawAt(x, y);
845 }
846 
onMouse(const MouseEvent & ev)847 bool ImageSlider::onMouse(const MouseEvent& ev)
848 {
849     if (ev.button != 1)
850         return false;
851 
852     if (ev.press)
853     {
854         if (! fSliderArea.contains(ev.pos))
855             return false;
856 
857         if ((ev.mod & kModifierShift) != 0 && fUsingDefault)
858         {
859             setValue(fValueDef, true);
860             fValueTmp = fValue;
861             return true;
862         }
863 
864         float vper;
865         const int x = ev.pos.getX();
866         const int y = ev.pos.getY();
867 
868         if (fStartPos.getY() == fEndPos.getY())
869         {
870             // horizontal
871             vper = float(x - fSliderArea.getX()) / float(fSliderArea.getWidth());
872         }
873         else
874         {
875             // vertical
876             vper = float(y - fSliderArea.getY()) / float(fSliderArea.getHeight());
877         }
878 
879         float value;
880 
881         if (fInverted)
882             value = fMaximum - vper * (fMaximum - fMinimum);
883         else
884             value = fMinimum + vper * (fMaximum - fMinimum);
885 
886         if (value < fMinimum)
887         {
888             fValueTmp = value = fMinimum;
889         }
890         else if (value > fMaximum)
891         {
892             fValueTmp = value = fMaximum;
893         }
894         else if (d_isNotZero(fStep))
895         {
896             fValueTmp = value;
897             const float rest = std::fmod(value, fStep);
898             value = value - rest + (rest > fStep/2.0f ? fStep : 0.0f);
899         }
900 
901         fDragging = true;
902         fStartedX = x;
903         fStartedY = y;
904 
905         if (fCallback != nullptr)
906             fCallback->imageSliderDragStarted(this);
907 
908         setValue(value, true);
909 
910         return true;
911     }
912     else if (fDragging)
913     {
914         if (fCallback != nullptr)
915             fCallback->imageSliderDragFinished(this);
916 
917         fDragging = false;
918         return true;
919     }
920 
921     return false;
922 }
923 
onMotion(const MotionEvent & ev)924 bool ImageSlider::onMotion(const MotionEvent& ev)
925 {
926     if (! fDragging)
927         return false;
928 
929     const bool horizontal = fStartPos.getY() == fEndPos.getY();
930     const int x = ev.pos.getX();
931     const int y = ev.pos.getY();
932 
933     if ((horizontal && fSliderArea.containsX(x)) || (fSliderArea.containsY(y) && ! horizontal))
934     {
935         float vper;
936 
937         if (horizontal)
938         {
939             // horizontal
940             vper = float(x - fSliderArea.getX()) / float(fSliderArea.getWidth());
941         }
942         else
943         {
944             // vertical
945             vper = float(y - fSliderArea.getY()) / float(fSliderArea.getHeight());
946         }
947 
948         float value;
949 
950         if (fInverted)
951             value = fMaximum - vper * (fMaximum - fMinimum);
952         else
953             value = fMinimum + vper * (fMaximum - fMinimum);
954 
955         if (value < fMinimum)
956         {
957             fValueTmp = value = fMinimum;
958         }
959         else if (value > fMaximum)
960         {
961             fValueTmp = value = fMaximum;
962         }
963         else if (d_isNotZero(fStep))
964         {
965             fValueTmp = value;
966             const float rest = std::fmod(value, fStep);
967             value = value - rest + (rest > fStep/2.0f ? fStep : 0.0f);
968         }
969 
970         setValue(value, true);
971     }
972     else if (horizontal)
973     {
974         if (x < fSliderArea.getX())
975             setValue(fInverted ? fMaximum : fMinimum, true);
976         else
977             setValue(fInverted ? fMinimum : fMaximum, true);
978     }
979     else
980     {
981         if (y < fSliderArea.getY())
982             setValue(fInverted ? fMaximum : fMinimum, true);
983         else
984             setValue(fInverted ? fMinimum : fMaximum, true);
985     }
986 
987     return true;
988 }
989 
_recheckArea()990 void ImageSlider::_recheckArea() noexcept
991 {
992     if (fStartPos.getY() == fEndPos.getY())
993     {
994         // horizontal
995         fSliderArea = Rectangle<int>(fStartPos.getX(),
996                                      fStartPos.getY(),
997                                      fEndPos.getX() + static_cast<int>(fImage.getWidth()) - fStartPos.getX(),
998                                      static_cast<int>(fImage.getHeight()));
999     }
1000     else
1001     {
1002         // vertical
1003         fSliderArea = Rectangle<int>(fStartPos.getX(),
1004                                      fStartPos.getY(),
1005                                      static_cast<int>(fImage.getWidth()),
1006                                      fEndPos.getY() + static_cast<int>(fImage.getHeight()) - fStartPos.getY());
1007     }
1008 }
1009 
1010 // -----------------------------------------------------------------------
1011 
ImageSwitch(Window & parent,const Image & imageNormal,const Image & imageDown)1012 ImageSwitch::ImageSwitch(Window& parent, const Image& imageNormal, const Image& imageDown) noexcept
1013     : Widget(parent),
1014       fImageNormal(imageNormal),
1015       fImageDown(imageDown),
1016       fIsDown(false),
1017       fCallback(nullptr)
1018 {
1019     DISTRHO_SAFE_ASSERT(fImageNormal.getSize() == fImageDown.getSize());
1020 
1021     setSize(fImageNormal.getSize());
1022 }
1023 
ImageSwitch(Widget * widget,const Image & imageNormal,const Image & imageDown)1024 ImageSwitch::ImageSwitch(Widget* widget, const Image& imageNormal, const Image& imageDown) noexcept
1025     : Widget(widget->getParentWindow()),
1026       fImageNormal(imageNormal),
1027       fImageDown(imageDown),
1028       fIsDown(false),
1029       fCallback(nullptr)
1030 {
1031     DISTRHO_SAFE_ASSERT(fImageNormal.getSize() == fImageDown.getSize());
1032 
1033     setSize(fImageNormal.getSize());
1034 }
1035 
ImageSwitch(const ImageSwitch & imageSwitch)1036 ImageSwitch::ImageSwitch(const ImageSwitch& imageSwitch) noexcept
1037     : Widget(imageSwitch.getParentWindow()),
1038       fImageNormal(imageSwitch.fImageNormal),
1039       fImageDown(imageSwitch.fImageDown),
1040       fIsDown(imageSwitch.fIsDown),
1041       fCallback(imageSwitch.fCallback)
1042 {
1043     DISTRHO_SAFE_ASSERT(fImageNormal.getSize() == fImageDown.getSize());
1044 
1045     setSize(fImageNormal.getSize());
1046 }
1047 
operator =(const ImageSwitch & imageSwitch)1048 ImageSwitch& ImageSwitch::operator=(const ImageSwitch& imageSwitch) noexcept
1049 {
1050     fImageNormal = imageSwitch.fImageNormal;
1051     fImageDown   = imageSwitch.fImageDown;
1052     fIsDown      = imageSwitch.fIsDown;
1053     fCallback    = imageSwitch.fCallback;
1054 
1055     DISTRHO_SAFE_ASSERT(fImageNormal.getSize() == fImageDown.getSize());
1056 
1057     setSize(fImageNormal.getSize());
1058 
1059     return *this;
1060 }
1061 
isDown() const1062 bool ImageSwitch::isDown() const noexcept
1063 {
1064     return fIsDown;
1065 }
1066 
setDown(bool down)1067 void ImageSwitch::setDown(bool down) noexcept
1068 {
1069     if (fIsDown == down)
1070         return;
1071 
1072     fIsDown = down;
1073     repaint();
1074 }
1075 
setCallback(Callback * callback)1076 void ImageSwitch::setCallback(Callback* callback) noexcept
1077 {
1078     fCallback = callback;
1079 }
1080 
onDisplay()1081 void ImageSwitch::onDisplay()
1082 {
1083     if (fIsDown)
1084         fImageDown.draw();
1085     else
1086         fImageNormal.draw();
1087 }
1088 
onMouse(const MouseEvent & ev)1089 bool ImageSwitch::onMouse(const MouseEvent& ev)
1090 {
1091     if (ev.press && contains(ev.pos))
1092     {
1093         fIsDown = !fIsDown;
1094 
1095         repaint();
1096 
1097         if (fCallback != nullptr)
1098             fCallback->imageSwitchClicked(this, fIsDown);
1099 
1100         return true;
1101     }
1102 
1103     return false;
1104 }
1105 
1106 // -----------------------------------------------------------------------
1107 
1108 END_NAMESPACE_DGL
1109