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