DPF

DISTRHO Plugin Framework
Log | Files | Refs | Submodules | README | LICENSE

ImageBaseWidgets.cpp (27230B)


      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