comparison DPF-Prymula-audioplugins/dpf/dgl/src/ImageBaseWidgets.cpp @ 3:84e66ea83026

DPF-Prymula-audioplugins-0.231015-2
author prymula <prymula76@outlook.com>
date Mon, 16 Oct 2023 21:53:34 +0200
parents
children
comparison
equal deleted inserted replaced
2:cf2cb71d31dd 3:84e66ea83026
1 /*
2 * DISTRHO Plugin Framework (DPF)
3 * Copyright (C) 2012-2022 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 "../ImageBaseWidgets.hpp"
18 #include "../Color.hpp"
19
20 START_NAMESPACE_DGL
21
22 // --------------------------------------------------------------------------------------------------------------------
23
24 template <class ImageType>
25 ImageBaseAboutWindow<ImageType>::ImageBaseAboutWindow(Window& transientParentWindow, const ImageType& image)
26 : StandaloneWindow(transientParentWindow.getApp(), transientParentWindow),
27 img(image)
28 {
29 setResizable(false);
30 setTitle("About");
31
32 if (image.isValid())
33 {
34 setSize(image.getSize());
35 setGeometryConstraints(image.getWidth(), image.getHeight(), true, true);
36 }
37
38 done();
39 }
40
41 template <class ImageType>
42 ImageBaseAboutWindow<ImageType>::ImageBaseAboutWindow(TopLevelWidget* const topLevelWidget, const ImageType& image)
43 : StandaloneWindow(topLevelWidget->getApp(), topLevelWidget->getWindow()),
44 img(image)
45 {
46 setResizable(false);
47 setTitle("About");
48
49 if (image.isValid())
50 {
51 setSize(image.getSize());
52 setGeometryConstraints(image.getWidth(), image.getHeight(), true, true);
53 }
54
55 done();
56 }
57
58 template <class ImageType>
59 void ImageBaseAboutWindow<ImageType>::setImage(const ImageType& image)
60 {
61 if (img == image)
62 return;
63
64 if (image.isInvalid())
65 {
66 img = image;
67 return;
68 }
69
70 reinit();
71
72 img = image;
73
74 setSize(image.getSize());
75 setGeometryConstraints(image.getWidth(), image.getHeight(), true, true);
76
77 done();
78 }
79
80 template <class ImageType>
81 void ImageBaseAboutWindow<ImageType>::onDisplay()
82 {
83 img.draw(getGraphicsContext());
84 }
85
86 template <class ImageType>
87 bool ImageBaseAboutWindow<ImageType>::onKeyboard(const KeyboardEvent& ev)
88 {
89 if (ev.press && ev.key == kKeyEscape)
90 {
91 close();
92 return true;
93 }
94
95 return false;
96 }
97
98 template <class ImageType>
99 bool ImageBaseAboutWindow<ImageType>::onMouse(const MouseEvent& ev)
100 {
101 if (ev.press)
102 {
103 close();
104 return true;
105 }
106
107 return false;
108 }
109
110 // --------------------------------------------------------------------------------------------------------------------
111
112 template <class ImageType>
113 struct ImageBaseButton<ImageType>::PrivateData : public ButtonEventHandler::Callback {
114 ImageBaseButton<ImageType>::Callback* callback;
115 ImageType imageNormal;
116 ImageType imageHover;
117 ImageType imageDown;
118
119 PrivateData(const ImageType& normal, const ImageType& hover, const ImageType& down)
120 : callback(nullptr),
121 imageNormal(normal),
122 imageHover(hover),
123 imageDown(down) {}
124
125 void buttonClicked(SubWidget* widget, int button) override
126 {
127 if (callback != nullptr)
128 if (ImageBaseButton* const imageButton = dynamic_cast<ImageBaseButton*>(widget))
129 callback->imageButtonClicked(imageButton, button);
130 }
131
132 DISTRHO_DECLARE_NON_COPYABLE(PrivateData)
133 };
134
135 // --------------------------------------------------------------------------------------------------------------------
136
137 template <class ImageType>
138 ImageBaseButton<ImageType>::ImageBaseButton(Widget* const parentWidget, const ImageType& image)
139 : SubWidget(parentWidget),
140 ButtonEventHandler(this),
141 pData(new PrivateData(image, image, image))
142 {
143 ButtonEventHandler::setCallback(pData);
144 setSize(image.getSize());
145 }
146
147 template <class ImageType>
148 ImageBaseButton<ImageType>::ImageBaseButton(Widget* const parentWidget, const ImageType& imageNormal, const ImageType& imageDown)
149 : SubWidget(parentWidget),
150 ButtonEventHandler(this),
151 pData(new PrivateData(imageNormal, imageNormal, imageDown))
152 {
153 DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize());
154
155 ButtonEventHandler::setCallback(pData);
156 setSize(imageNormal.getSize());
157 }
158
159 template <class ImageType>
160 ImageBaseButton<ImageType>::ImageBaseButton(Widget* const parentWidget, const ImageType& imageNormal, const ImageType& imageHover, const ImageType& imageDown)
161 : SubWidget(parentWidget),
162 ButtonEventHandler(this),
163 pData(new PrivateData(imageNormal, imageHover, imageDown))
164 {
165 DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageHover.getSize() && imageHover.getSize() == imageDown.getSize());
166
167 ButtonEventHandler::setCallback(pData);
168 setSize(imageNormal.getSize());
169 }
170
171 template <class ImageType>
172 ImageBaseButton<ImageType>::~ImageBaseButton()
173 {
174 delete pData;
175 }
176
177 template <class ImageType>
178 void ImageBaseButton<ImageType>::setCallback(Callback* callback) noexcept
179 {
180 pData->callback = callback;
181 }
182
183 template <class ImageType>
184 void ImageBaseButton<ImageType>::onDisplay()
185 {
186 const GraphicsContext& context(getGraphicsContext());
187
188 const State state = ButtonEventHandler::getState();
189
190 if (ButtonEventHandler::isCheckable())
191 {
192 if (ButtonEventHandler::isChecked())
193 pData->imageDown.draw(context);
194 else if (state & kButtonStateHover)
195 pData->imageHover.draw(context);
196 else
197 pData->imageNormal.draw(context);
198 }
199 else
200 {
201 if (state & kButtonStateActive)
202 pData->imageDown.draw(context);
203 else if (state & kButtonStateHover)
204 pData->imageHover.draw(context);
205 else
206 pData->imageNormal.draw(context);
207 }
208 }
209
210 template <class ImageType>
211 bool ImageBaseButton<ImageType>::onMouse(const MouseEvent& ev)
212 {
213 if (SubWidget::onMouse(ev))
214 return true;
215 return ButtonEventHandler::mouseEvent(ev);
216 }
217
218 template <class ImageType>
219 bool ImageBaseButton<ImageType>::onMotion(const MotionEvent& ev)
220 {
221 if (SubWidget::onMotion(ev))
222 return true;
223 return ButtonEventHandler::motionEvent(ev);
224 }
225
226 // --------------------------------------------------------------------------------------------------------------------
227
228 template <class ImageType>
229 struct ImageBaseKnob<ImageType>::PrivateData : public KnobEventHandler::Callback {
230 ImageBaseKnob<ImageType>::Callback* callback;
231 ImageType image;
232
233 int rotationAngle;
234
235 bool alwaysRepaint;
236 bool isImgVertical;
237 uint imgLayerWidth;
238 uint imgLayerHeight;
239 uint imgLayerCount;
240 bool isReady;
241
242 union {
243 uint glTextureId;
244 void* cairoSurface;
245 };
246
247 explicit PrivateData(const ImageType& img)
248 : callback(nullptr),
249 image(img),
250 rotationAngle(0),
251 alwaysRepaint(false),
252 isImgVertical(img.getHeight() > img.getWidth()),
253 imgLayerWidth(isImgVertical ? img.getWidth() : img.getHeight()),
254 imgLayerHeight(imgLayerWidth),
255 imgLayerCount(isImgVertical ? img.getHeight()/imgLayerHeight : img.getWidth()/imgLayerWidth),
256 isReady(false)
257 {
258 init();
259 }
260
261 explicit PrivateData(PrivateData* const other)
262 : callback(other->callback),
263 image(other->image),
264 rotationAngle(other->rotationAngle),
265 alwaysRepaint(other->alwaysRepaint),
266 isImgVertical(other->isImgVertical),
267 imgLayerWidth(other->imgLayerWidth),
268 imgLayerHeight(other->imgLayerHeight),
269 imgLayerCount(other->imgLayerCount),
270 isReady(false)
271 {
272 init();
273 }
274
275 void assignFrom(PrivateData* const other)
276 {
277 cleanup();
278 image = other->image;
279 rotationAngle = other->rotationAngle;
280 callback = other->callback;
281 alwaysRepaint = other->alwaysRepaint;
282 isImgVertical = other->isImgVertical;
283 imgLayerWidth = other->imgLayerWidth;
284 imgLayerHeight = other->imgLayerHeight;
285 imgLayerCount = other->imgLayerCount;
286 isReady = false;
287 init();
288 }
289
290 ~PrivateData()
291 {
292 cleanup();
293 }
294
295 void knobDragStarted(SubWidget* const widget) override
296 {
297 if (callback != nullptr)
298 if (ImageBaseKnob* const imageKnob = dynamic_cast<ImageBaseKnob*>(widget))
299 callback->imageKnobDragStarted(imageKnob);
300 }
301
302 void knobDragFinished(SubWidget* const widget) override
303 {
304 if (callback != nullptr)
305 if (ImageBaseKnob* const imageKnob = dynamic_cast<ImageBaseKnob*>(widget))
306 callback->imageKnobDragFinished(imageKnob);
307 }
308
309 void knobValueChanged(SubWidget* const widget, const float value) override
310 {
311 if (rotationAngle == 0 || alwaysRepaint)
312 isReady = false;
313
314 if (callback != nullptr)
315 if (ImageBaseKnob* const imageKnob = dynamic_cast<ImageBaseKnob*>(widget))
316 callback->imageKnobValueChanged(imageKnob, value);
317 }
318
319 // implemented independently per graphics backend
320 void init();
321 void cleanup();
322
323 DISTRHO_DECLARE_NON_COPYABLE(PrivateData)
324 };
325
326 // --------------------------------------------------------------------------------------------------------------------
327
328 template <class ImageType>
329 ImageBaseKnob<ImageType>::ImageBaseKnob(Widget* const parentWidget,
330 const ImageType& image,
331 const Orientation orientation) noexcept
332 : SubWidget(parentWidget),
333 KnobEventHandler(this),
334 pData(new PrivateData(image))
335 {
336 KnobEventHandler::setCallback(pData);
337 setOrientation(orientation);
338 setSize(pData->imgLayerWidth, pData->imgLayerHeight);
339 }
340
341 template <class ImageType>
342 ImageBaseKnob<ImageType>::ImageBaseKnob(const ImageBaseKnob<ImageType>& imageKnob)
343 : SubWidget(imageKnob.getParentWidget()),
344 KnobEventHandler(this, imageKnob),
345 pData(new PrivateData(imageKnob.pData))
346 {
347 KnobEventHandler::setCallback(pData);
348 setOrientation(imageKnob.getOrientation());
349 setSize(pData->imgLayerWidth, pData->imgLayerHeight);
350 }
351
352 template <class ImageType>
353 ImageBaseKnob<ImageType>& ImageBaseKnob<ImageType>::operator=(const ImageBaseKnob<ImageType>& imageKnob)
354 {
355 KnobEventHandler::operator=(imageKnob);
356 pData->assignFrom(imageKnob.pData);
357 setSize(pData->imgLayerWidth, pData->imgLayerHeight);
358 return *this;
359 }
360
361 template <class ImageType>
362 ImageBaseKnob<ImageType>::~ImageBaseKnob()
363 {
364 delete pData;
365 }
366
367 template <class ImageType>
368 void ImageBaseKnob<ImageType>::setCallback(Callback* callback) noexcept
369 {
370 pData->callback = callback;
371 }
372
373 template <class ImageType>
374 void ImageBaseKnob<ImageType>::setImageLayerCount(uint count) noexcept
375 {
376 DISTRHO_SAFE_ASSERT_RETURN(count > 1,);
377
378 pData->imgLayerCount = count;
379
380 if (pData->isImgVertical)
381 pData->imgLayerHeight = pData->image.getHeight()/count;
382 else
383 pData->imgLayerWidth = pData->image.getWidth()/count;
384
385 setSize(pData->imgLayerWidth, pData->imgLayerHeight);
386 }
387
388 template <class ImageType>
389 void ImageBaseKnob<ImageType>::setRotationAngle(int angle)
390 {
391 if (pData->rotationAngle == angle)
392 return;
393
394 pData->rotationAngle = angle;
395 pData->isReady = false;
396 }
397
398 template <class ImageType>
399 bool ImageBaseKnob<ImageType>::setValue(float value, bool sendCallback) noexcept
400 {
401 if (KnobEventHandler::setValue(value, sendCallback))
402 {
403 if (pData->rotationAngle == 0 || pData->alwaysRepaint)
404 pData->isReady = false;
405
406 return true;
407 }
408
409 return false;
410 }
411
412 template <class ImageType>
413 bool ImageBaseKnob<ImageType>::onMouse(const MouseEvent& ev)
414 {
415 if (SubWidget::onMouse(ev))
416 return true;
417 return KnobEventHandler::mouseEvent(ev, getTopLevelWidget()->getScaleFactor());
418 }
419
420 template <class ImageType>
421 bool ImageBaseKnob<ImageType>::onMotion(const MotionEvent& ev)
422 {
423 if (SubWidget::onMotion(ev))
424 return true;
425 return KnobEventHandler::motionEvent(ev, getTopLevelWidget()->getScaleFactor());
426 }
427
428 template <class ImageType>
429 bool ImageBaseKnob<ImageType>::onScroll(const ScrollEvent& ev)
430 {
431 if (SubWidget::onScroll(ev))
432 return true;
433 return KnobEventHandler::scrollEvent(ev);
434 }
435
436 // --------------------------------------------------------------------------------------------------------------------
437
438 template <class ImageType>
439 struct ImageBaseSlider<ImageType>::PrivateData {
440 ImageType image;
441 float minimum;
442 float maximum;
443 float step;
444 float value;
445 float valueDef;
446 float valueTmp;
447 bool usingDefault;
448
449 bool dragging;
450 bool checkable;
451 bool inverted;
452 bool valueIsSet;
453 double startedX;
454 double startedY;
455
456 Callback* callback;
457
458 Point<int> startPos;
459 Point<int> endPos;
460 Rectangle<double> sliderArea;
461
462 PrivateData(const ImageType& img)
463 : image(img),
464 minimum(0.0f),
465 maximum(1.0f),
466 step(0.0f),
467 value(0.5f),
468 valueDef(value),
469 valueTmp(value),
470 usingDefault(false),
471 dragging(false),
472 checkable(false),
473 inverted(false),
474 valueIsSet(false),
475 startedX(0.0),
476 startedY(0.0),
477 callback(nullptr),
478 startPos(),
479 endPos(),
480 sliderArea() {}
481
482 void recheckArea() noexcept
483 {
484 if (startPos.getY() == endPos.getY())
485 {
486 // horizontal
487 sliderArea = Rectangle<double>(startPos.getX(),
488 startPos.getY(),
489 endPos.getX() + static_cast<int>(image.getWidth()) - startPos.getX(),
490 static_cast<int>(image.getHeight()));
491 }
492 else
493 {
494 // vertical
495 sliderArea = Rectangle<double>(startPos.getX(),
496 startPos.getY(),
497 static_cast<int>(image.getWidth()),
498 endPos.getY() + static_cast<int>(image.getHeight()) - startPos.getY());
499 }
500 }
501
502 DISTRHO_DECLARE_NON_COPYABLE(PrivateData)
503 };
504
505 // --------------------------------------------------------------------------------------------------------------------
506
507 template <class ImageType>
508 ImageBaseSlider<ImageType>::ImageBaseSlider(Widget* const parentWidget, const ImageType& image) noexcept
509 : SubWidget(parentWidget),
510 pData(new PrivateData(image))
511 {
512 setNeedsFullViewportDrawing();
513 }
514
515 template <class ImageType>
516 ImageBaseSlider<ImageType>::~ImageBaseSlider()
517 {
518 delete pData;
519 }
520
521 template <class ImageType>
522 float ImageBaseSlider<ImageType>::getValue() const noexcept
523 {
524 return pData->value;
525 }
526
527 template <class ImageType>
528 void ImageBaseSlider<ImageType>::setValue(float value, bool sendCallback) noexcept
529 {
530 if (! pData->valueIsSet)
531 pData->valueIsSet = true;
532
533 if (d_isEqual(pData->value, value))
534 return;
535
536 pData->value = value;
537
538 if (d_isZero(pData->step))
539 pData->valueTmp = value;
540
541 repaint();
542
543 if (sendCallback && pData->callback != nullptr)
544 {
545 try {
546 pData->callback->imageSliderValueChanged(this, pData->value);
547 } DISTRHO_SAFE_EXCEPTION("ImageBaseSlider::setValue");
548 }
549 }
550
551 template <class ImageType>
552 void ImageBaseSlider<ImageType>::setStartPos(const Point<int>& startPos) noexcept
553 {
554 pData->startPos = startPos;
555 pData->recheckArea();
556 }
557
558 template <class ImageType>
559 void ImageBaseSlider<ImageType>::setStartPos(int x, int y) noexcept
560 {
561 setStartPos(Point<int>(x, y));
562 }
563
564 template <class ImageType>
565 void ImageBaseSlider<ImageType>::setEndPos(const Point<int>& endPos) noexcept
566 {
567 pData->endPos = endPos;
568 pData->recheckArea();
569 }
570
571 template <class ImageType>
572 void ImageBaseSlider<ImageType>::setEndPos(int x, int y) noexcept
573 {
574 setEndPos(Point<int>(x, y));
575 }
576
577 template <class ImageType>
578 void ImageBaseSlider<ImageType>::setCheckable(bool checkable) noexcept
579 {
580 if (pData->checkable == checkable)
581 return;
582
583 pData->checkable = checkable;
584 repaint();
585 }
586
587 template <class ImageType>
588 void ImageBaseSlider<ImageType>::setInverted(bool inverted) noexcept
589 {
590 if (pData->inverted == inverted)
591 return;
592
593 pData->inverted = inverted;
594 repaint();
595 }
596
597 template <class ImageType>
598 void ImageBaseSlider<ImageType>::setDefault(float value) noexcept
599 {
600 pData->valueDef = value;
601 pData->usingDefault = true;
602 }
603
604 template <class ImageType>
605 void ImageBaseSlider<ImageType>::setRange(float min, float max) noexcept
606 {
607 pData->minimum = min;
608 pData->maximum = max;
609
610 if (pData->value < min)
611 {
612 pData->value = min;
613 repaint();
614
615 if (pData->callback != nullptr && pData->valueIsSet)
616 {
617 try {
618 pData->callback->imageSliderValueChanged(this, pData->value);
619 } DISTRHO_SAFE_EXCEPTION("ImageBaseSlider::setRange < min");
620 }
621 }
622 else if (pData->value > max)
623 {
624 pData->value = max;
625 repaint();
626
627 if (pData->callback != nullptr && pData->valueIsSet)
628 {
629 try {
630 pData->callback->imageSliderValueChanged(this, pData->value);
631 } DISTRHO_SAFE_EXCEPTION("ImageBaseSlider::setRange > max");
632 }
633 }
634 }
635
636 template <class ImageType>
637 void ImageBaseSlider<ImageType>::setStep(float step) noexcept
638 {
639 pData->step = step;
640 }
641
642 template <class ImageType>
643 void ImageBaseSlider<ImageType>::setCallback(Callback* callback) noexcept
644 {
645 pData->callback = callback;
646 }
647
648 template <class ImageType>
649 void ImageBaseSlider<ImageType>::onDisplay()
650 {
651 const GraphicsContext& context(getGraphicsContext());
652
653 #if 0 // DEBUG, paints slider area
654 Color(1.0f, 1.0f, 1.0f, 0.5f).setFor(context, true);
655 Rectangle<int>(pData->sliderArea.getX(),
656 pData->sliderArea.getY(),
657 pData->sliderArea.getX()+pData->sliderArea.getWidth(),
658 pData->sliderArea.getY()+pData->sliderArea.getHeight()).draw(context);
659 Color(1.0f, 1.0f, 1.0f, 1.0f).setFor(context, true);
660 #endif
661
662 const float normValue = (pData->value - pData->minimum) / (pData->maximum - pData->minimum);
663
664 int x, y;
665
666 if (pData->startPos.getY() == pData->endPos.getY())
667 {
668 // horizontal
669 if (pData->inverted)
670 x = pData->endPos.getX() - static_cast<int>(normValue*static_cast<float>(pData->endPos.getX()-pData->startPos.getX()));
671 else
672 x = pData->startPos.getX() + static_cast<int>(normValue*static_cast<float>(pData->endPos.getX()-pData->startPos.getX()));
673
674 y = pData->startPos.getY();
675 }
676 else
677 {
678 // vertical
679 x = pData->startPos.getX();
680
681 if (pData->inverted)
682 y = pData->endPos.getY() - static_cast<int>(normValue*static_cast<float>(pData->endPos.getY()-pData->startPos.getY()));
683 else
684 y = pData->startPos.getY() + static_cast<int>(normValue*static_cast<float>(pData->endPos.getY()-pData->startPos.getY()));
685 }
686
687 pData->image.drawAt(context, x, y);
688 }
689
690 template <class ImageType>
691 bool ImageBaseSlider<ImageType>::onMouse(const MouseEvent& ev)
692 {
693 if (ev.button != 1)
694 return false;
695
696 if (ev.press)
697 {
698 if (! pData->sliderArea.contains(ev.pos))
699 return false;
700
701 if ((ev.mod & kModifierShift) != 0 && pData->usingDefault)
702 {
703 setValue(pData->valueDef, true);
704 pData->valueTmp = pData->value;
705 return true;
706 }
707
708 if (pData->checkable)
709 {
710 const float value = d_isEqual(pData->valueTmp, pData->minimum) ? pData->maximum : pData->minimum;
711 setValue(value, true);
712 pData->valueTmp = pData->value;
713 return true;
714 }
715
716 float vper;
717 const double x = ev.pos.getX();
718 const double y = ev.pos.getY();
719
720 if (pData->startPos.getY() == pData->endPos.getY())
721 {
722 // horizontal
723 vper = float(x - pData->sliderArea.getX()) / float(pData->sliderArea.getWidth());
724 }
725 else
726 {
727 // vertical
728 vper = float(y - pData->sliderArea.getY()) / float(pData->sliderArea.getHeight());
729 }
730
731 float value;
732
733 if (pData->inverted)
734 value = pData->maximum - vper * (pData->maximum - pData->minimum);
735 else
736 value = pData->minimum + vper * (pData->maximum - pData->minimum);
737
738 if (value < pData->minimum)
739 {
740 pData->valueTmp = value = pData->minimum;
741 }
742 else if (value > pData->maximum)
743 {
744 pData->valueTmp = value = pData->maximum;
745 }
746 else if (d_isNotZero(pData->step))
747 {
748 pData->valueTmp = value;
749 const float rest = std::fmod(value, pData->step);
750 value = value - rest + (rest > pData->step/2.0f ? pData->step : 0.0f);
751 }
752
753 pData->dragging = true;
754 pData->startedX = x;
755 pData->startedY = y;
756
757 if (pData->callback != nullptr)
758 pData->callback->imageSliderDragStarted(this);
759
760 setValue(value, true);
761
762 return true;
763 }
764 else if (pData->dragging)
765 {
766 if (pData->callback != nullptr)
767 pData->callback->imageSliderDragFinished(this);
768
769 pData->dragging = false;
770 return true;
771 }
772
773 return false;
774 }
775
776 template <class ImageType>
777 bool ImageBaseSlider<ImageType>::onMotion(const MotionEvent& ev)
778 {
779 if (! pData->dragging)
780 return false;
781
782 const bool horizontal = pData->startPos.getY() == pData->endPos.getY();
783 const double x = ev.pos.getX();
784 const double y = ev.pos.getY();
785
786 if ((horizontal && pData->sliderArea.containsX(x)) || (pData->sliderArea.containsY(y) && ! horizontal))
787 {
788 float vper;
789
790 if (horizontal)
791 {
792 // horizontal
793 vper = float(x - pData->sliderArea.getX()) / float(pData->sliderArea.getWidth());
794 }
795 else
796 {
797 // vertical
798 vper = float(y - pData->sliderArea.getY()) / float(pData->sliderArea.getHeight());
799 }
800
801 float value;
802
803 if (pData->inverted)
804 value = pData->maximum - vper * (pData->maximum - pData->minimum);
805 else
806 value = pData->minimum + vper * (pData->maximum - pData->minimum);
807
808 if (value < pData->minimum)
809 {
810 pData->valueTmp = value = pData->minimum;
811 }
812 else if (value > pData->maximum)
813 {
814 pData->valueTmp = value = pData->maximum;
815 }
816 else if (d_isNotZero(pData->step))
817 {
818 pData->valueTmp = value;
819 const float rest = std::fmod(value, pData->step);
820 value = value - rest + (rest > pData->step/2.0f ? pData->step : 0.0f);
821 }
822
823 setValue(value, true);
824 }
825 else if (horizontal)
826 {
827 if (x < pData->sliderArea.getX())
828 setValue(pData->inverted ? pData->maximum : pData->minimum, true);
829 else
830 setValue(pData->inverted ? pData->minimum : pData->maximum, true);
831 }
832 else
833 {
834 if (y < pData->sliderArea.getY())
835 setValue(pData->inverted ? pData->maximum : pData->minimum, true);
836 else
837 setValue(pData->inverted ? pData->minimum : pData->maximum, true);
838 }
839
840 return true;
841 }
842
843 // --------------------------------------------------------------------------------------------------------------------
844
845 template <class ImageType>
846 struct ImageBaseSwitch<ImageType>::PrivateData {
847 ImageType imageNormal;
848 ImageType imageDown;
849 bool isDown;
850 Callback* callback;
851
852 PrivateData(const ImageType& normal, const ImageType& down)
853 : imageNormal(normal),
854 imageDown(down),
855 isDown(false),
856 callback(nullptr)
857 {
858 DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize());
859 }
860
861 PrivateData(PrivateData* const other)
862 : imageNormal(other->imageNormal),
863 imageDown(other->imageDown),
864 isDown(other->isDown),
865 callback(other->callback)
866 {
867 DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize());
868 }
869
870 void assignFrom(PrivateData* const other)
871 {
872 imageNormal = other->imageNormal;
873 imageDown = other->imageDown;
874 isDown = other->isDown;
875 callback = other->callback;
876 DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize());
877 }
878
879 DISTRHO_DECLARE_NON_COPYABLE(PrivateData)
880 };
881
882 // --------------------------------------------------------------------------------------------------------------------
883
884 template <class ImageType>
885 ImageBaseSwitch<ImageType>::ImageBaseSwitch(Widget* const parentWidget, const ImageType& imageNormal, const ImageType& imageDown) noexcept
886 : SubWidget(parentWidget),
887 pData(new PrivateData(imageNormal, imageDown))
888 {
889 setSize(imageNormal.getSize());
890 }
891
892 template <class ImageType>
893 ImageBaseSwitch<ImageType>::ImageBaseSwitch(const ImageBaseSwitch<ImageType>& imageSwitch) noexcept
894 : SubWidget(imageSwitch.getParentWidget()),
895 pData(new PrivateData(imageSwitch.pData))
896 {
897 setSize(pData->imageNormal.getSize());
898 }
899
900 template <class ImageType>
901 ImageBaseSwitch<ImageType>& ImageBaseSwitch<ImageType>::operator=(const ImageBaseSwitch<ImageType>& imageSwitch) noexcept
902 {
903 pData->assignFrom(imageSwitch.pData);
904 setSize(pData->imageNormal.getSize());
905 return *this;
906 }
907
908 template <class ImageType>
909 ImageBaseSwitch<ImageType>::~ImageBaseSwitch()
910 {
911 delete pData;
912 }
913
914 template <class ImageType>
915 bool ImageBaseSwitch<ImageType>::isDown() const noexcept
916 {
917 return pData->isDown;
918 }
919
920 template <class ImageType>
921 void ImageBaseSwitch<ImageType>::setDown(const bool down) noexcept
922 {
923 if (pData->isDown == down)
924 return;
925
926 pData->isDown = down;
927 repaint();
928 }
929
930 template <class ImageType>
931 void ImageBaseSwitch<ImageType>::setCallback(Callback* const callback) noexcept
932 {
933 pData->callback = callback;
934 }
935
936 template <class ImageType>
937 void ImageBaseSwitch<ImageType>::onDisplay()
938 {
939 const GraphicsContext& context(getGraphicsContext());
940
941 if (pData->isDown)
942 pData->imageDown.draw(context);
943 else
944 pData->imageNormal.draw(context);
945 }
946
947 template <class ImageType>
948 bool ImageBaseSwitch<ImageType>::onMouse(const MouseEvent& ev)
949 {
950 if (ev.press && contains(ev.pos))
951 {
952 pData->isDown = !pData->isDown;
953
954 repaint();
955
956 if (pData->callback != nullptr)
957 pData->callback->imageSwitchClicked(this, pData->isDown);
958
959 return true;
960 }
961
962 return false;
963 }
964
965 // --------------------------------------------------------------------------------------------------------------------
966
967 END_NAMESPACE_DGL