/* * DISTRHO Plugin Framework (DPF) * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this * permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "../ImageBaseWidgets.hpp" #include "../Color.hpp" #include "Common.hpp" START_NAMESPACE_DGL // -------------------------------------------------------------------------------------------------------------------- template ImageBaseAboutWindow::ImageBaseAboutWindow(Window& parentWindow, const ImageType& image) : StandaloneWindow(parentWindow.getApp(), parentWindow), img(image) { setResizable(false); setTitle("About"); if (image.isValid()) setSize(image.getSize()); } template ImageBaseAboutWindow::ImageBaseAboutWindow(TopLevelWidget* const parentTopLevelWidget, const ImageType& image) : StandaloneWindow(parentTopLevelWidget->getApp(), parentTopLevelWidget->getWindow()), img(image) { setResizable(false); setTitle("About"); if (image.isValid()) setSize(image.getSize()); } template void ImageBaseAboutWindow::setImage(const ImageType& image) { if (img == image) return; img = image; setSize(image.getSize()); } template void ImageBaseAboutWindow::onDisplay() { img.draw(getGraphicsContext()); } template bool ImageBaseAboutWindow::onKeyboard(const KeyboardEvent& ev) { if (ev.press && ev.key == kKeyEscape) { close(); return true; } return false; } template bool ImageBaseAboutWindow::onMouse(const MouseEvent& ev) { if (ev.press) { close(); return true; } return false; } // -------------------------------------------------------------------------------------------------------------------- template struct ImageBaseButton::PrivateData { ButtonImpl impl; ImageType imageNormal; ImageType imageHover; ImageType imageDown; PrivateData(ImageBaseButton* const s, const ImageType& normal, const ImageType& hover, const ImageType& down) : impl(s), imageNormal(normal), imageHover(hover), imageDown(down) {} DISTRHO_DECLARE_NON_COPYABLE(PrivateData) }; // -------------------------------------------------------------------------------------------------------------------- template ImageBaseButton::ImageBaseButton(Widget* const parentWidget, const ImageType& image) : SubWidget(parentWidget), pData(new PrivateData(this, image, image, image)) { setSize(image.getSize()); } template ImageBaseButton::ImageBaseButton(Widget* const parentWidget, const ImageType& imageNormal, const ImageType& imageDown) : SubWidget(parentWidget), pData(new PrivateData(this, imageNormal, imageNormal, imageDown)) { DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize()); setSize(imageNormal.getSize()); } template ImageBaseButton::ImageBaseButton(Widget* const parentWidget, const ImageType& imageNormal, const ImageType& imageHover, const ImageType& imageDown) : SubWidget(parentWidget), pData(new PrivateData(this, imageNormal, imageHover, imageDown)) { DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageHover.getSize() && imageHover.getSize() == imageDown.getSize()); setSize(imageNormal.getSize()); } template ImageBaseButton::~ImageBaseButton() { delete pData; } template void ImageBaseButton::setCallback(Callback* callback) noexcept { pData->impl.callback_img = callback; } template void ImageBaseButton::onDisplay() { const GraphicsContext& context(getGraphicsContext()); switch (pData->impl.state) { case ButtonImpl::kStateDown: pData->imageDown.draw(context); break; case ButtonImpl::kStateHover: pData->imageHover.draw(context); break; default: pData->imageNormal.draw(context); break; } } template bool ImageBaseButton::onMouse(const MouseEvent& ev) { return pData->impl.onMouse(ev); } template bool ImageBaseButton::onMotion(const MotionEvent& ev) { return pData->impl.onMotion(ev); } // -------------------------------------------------------------------------------------------------------------------- template ImageBaseKnob::PrivateData::PrivateData(const ImageType& img, const Orientation o) : image(img), minimum(0.0f), maximum(1.0f), step(0.0f), value(0.5f), valueDef(value), valueTmp(value), usingDefault(false), usingLog(false), orientation(o), rotationAngle(0), dragging(false), lastX(0.0), lastY(0.0), callback(nullptr), alwaysRepaint(false), isImgVertical(img.getHeight() > img.getWidth()), imgLayerWidth(isImgVertical ? img.getWidth() : img.getHeight()), imgLayerHeight(imgLayerWidth), imgLayerCount(isImgVertical ? img.getHeight()/imgLayerHeight : img.getWidth()/imgLayerWidth), isReady(false) { init(); } template ImageBaseKnob::PrivateData::PrivateData(PrivateData* const other) : image(other->image), minimum(other->minimum), maximum(other->maximum), step(other->step), value(other->value), valueDef(other->valueDef), valueTmp(value), usingDefault(other->usingDefault), usingLog(other->usingLog), orientation(other->orientation), rotationAngle(other->rotationAngle), dragging(false), lastX(0.0), lastY(0.0), callback(other->callback), alwaysRepaint(other->alwaysRepaint), isImgVertical(other->isImgVertical), imgLayerWidth(other->imgLayerWidth), imgLayerHeight(other->imgLayerHeight), imgLayerCount(other->imgLayerCount), isReady(false) { init(); } template void ImageBaseKnob::PrivateData::assignFrom(PrivateData* const other) { cleanup(); image = other->image; minimum = other->minimum; maximum = other->maximum; step = other->step; value = other->value; valueDef = other->valueDef; valueTmp = value; usingDefault = other->usingDefault; usingLog = other->usingLog; orientation = other->orientation; rotationAngle = other->rotationAngle; dragging = false; lastX = 0.0; lastY = 0.0; callback = other->callback; alwaysRepaint = other->alwaysRepaint; isImgVertical = other->isImgVertical; imgLayerWidth = other->imgLayerWidth; imgLayerHeight = other->imgLayerHeight; imgLayerCount = other->imgLayerCount; isReady = false; init(); } // -------------------------------------------------------------------------------------------------------------------- template ImageBaseKnob::ImageBaseKnob(Widget* const parentWidget, const ImageType& image, const Orientation orientation) noexcept : SubWidget(parentWidget), pData(new PrivateData(image, orientation)) { setSize(pData->imgLayerWidth, pData->imgLayerHeight); } template ImageBaseKnob::ImageBaseKnob(const ImageBaseKnob& imageKnob) : SubWidget(imageKnob.getParentWidget()), pData(new PrivateData(imageKnob.pData)) { setSize(pData->imgLayerWidth, pData->imgLayerHeight); } template ImageBaseKnob& ImageBaseKnob::operator=(const ImageBaseKnob& imageKnob) { pData->assignFrom(imageKnob.pData); setSize(pData->imgLayerWidth, pData->imgLayerHeight); return *this; } template ImageBaseKnob::~ImageBaseKnob() { delete pData; } template float ImageBaseKnob::getValue() const noexcept { return pData->value; } // NOTE: value is assumed to be scaled if using log template void ImageBaseKnob::setDefault(float value) noexcept { pData->valueDef = value; pData->usingDefault = true; } template void ImageBaseKnob::setRange(float min, float max) noexcept { DISTRHO_SAFE_ASSERT_RETURN(max > min,); if (pData->value < min) { pData->value = min; repaint(); if (pData->callback != nullptr) { try { pData->callback->imageKnobValueChanged(this, pData->value); } DISTRHO_SAFE_EXCEPTION("ImageBaseKnob::setRange < min"); } } else if (pData->value > max) { pData->value = max; repaint(); if (pData->callback != nullptr) { try { pData->callback->imageKnobValueChanged(this, pData->value); } DISTRHO_SAFE_EXCEPTION("ImageBaseKnob::setRange > max"); } } pData->minimum = min; pData->maximum = max; } template void ImageBaseKnob::setStep(float step) noexcept { pData->step = step; } // NOTE: value is assumed to be scaled if using log template void ImageBaseKnob::setValue(float value, bool sendCallback) noexcept { if (d_isEqual(pData->value, value)) return; pData->value = value; if (d_isZero(pData->step)) pData->valueTmp = value; if (pData->rotationAngle == 0 || pData->alwaysRepaint) pData->isReady = false; repaint(); if (sendCallback && pData->callback != nullptr) { try { pData->callback->imageKnobValueChanged(this, pData->value); } DISTRHO_SAFE_EXCEPTION("ImageBaseKnob::setValue"); } } template void ImageBaseKnob::setUsingLogScale(bool yesNo) noexcept { pData->usingLog = yesNo; } template void ImageBaseKnob::setCallback(Callback* callback) noexcept { pData->callback = callback; } template void ImageBaseKnob::setOrientation(Orientation orientation) noexcept { if (pData->orientation == orientation) return; pData->orientation = orientation; } template void ImageBaseKnob::setRotationAngle(int angle) { if (pData->rotationAngle == angle) return; pData->rotationAngle = angle; pData->isReady = false; } template void ImageBaseKnob::setImageLayerCount(uint count) noexcept { DISTRHO_SAFE_ASSERT_RETURN(count > 1,); pData->imgLayerCount = count; if (pData->isImgVertical) pData->imgLayerHeight = pData->image.getHeight()/count; else pData->imgLayerWidth = pData->image.getWidth()/count; setSize(pData->imgLayerWidth, pData->imgLayerHeight); } template bool ImageBaseKnob::onMouse(const MouseEvent& ev) { if (ev.button != 1) return false; if (ev.press) { if (! contains(ev.pos)) return false; if ((ev.mod & kModifierShift) != 0 && pData->usingDefault) { setValue(pData->valueDef, true); pData->valueTmp = pData->value; return true; } pData->dragging = true; pData->lastX = ev.pos.getX(); pData->lastY = ev.pos.getY(); if (pData->callback != nullptr) pData->callback->imageKnobDragStarted(this); return true; } else if (pData->dragging) { if (pData->callback != nullptr) pData->callback->imageKnobDragFinished(this); pData->dragging = false; return true; } return false; } template bool ImageBaseKnob::onMotion(const MotionEvent& ev) { if (! pData->dragging) return false; bool doVal = false; float d, value = 0.0f; if (pData->orientation == ImageBaseKnob::Horizontal) { if (const double movX = ev.pos.getX() - pData->lastX) { d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; value = (pData->usingLog ? pData->invlogscale(pData->valueTmp) : pData->valueTmp) + (float(pData->maximum - pData->minimum) / d * float(movX)); doVal = true; } } else if (pData->orientation == ImageBaseKnob::Vertical) { if (const double movY = pData->lastY - ev.pos.getY()) { d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; value = (pData->usingLog ? pData->invlogscale(pData->valueTmp) : pData->valueTmp) + (float(pData->maximum - pData->minimum) / d * float(movY)); doVal = true; } } if (! doVal) return false; if (pData->usingLog) value = pData->logscale(value); if (value < pData->minimum) { pData->valueTmp = value = pData->minimum; } else if (value > pData->maximum) { pData->valueTmp = value = pData->maximum; } else if (d_isNotZero(pData->step)) { pData->valueTmp = value; const float rest = std::fmod(value, pData->step); value = value - rest + (rest > pData->step/2.0f ? pData->step : 0.0f); } setValue(value, true); pData->lastX = ev.pos.getX(); pData->lastY = ev.pos.getY(); return true; } template bool ImageBaseKnob::onScroll(const ScrollEvent& ev) { if (! contains(ev.pos)) return false; const float dir = (ev.delta.getY() > 0.f) ? 1.f : -1.f; const float d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; float value = (pData->usingLog ? pData->invlogscale(pData->valueTmp) : pData->valueTmp) + ((pData->maximum - pData->minimum) / d * 10.f * dir); if (pData->usingLog) value = pData->logscale(value); if (value < pData->minimum) { pData->valueTmp = value = pData->minimum; } else if (value > pData->maximum) { pData->valueTmp = value = pData->maximum; } else if (d_isNotZero(pData->step)) { pData->valueTmp = value; const float rest = std::fmod(value, pData->step); value = value - rest + (rest > pData->step/2.0f ? pData->step : 0.0f); } setValue(value, true); return true; } // -------------------------------------------------------------------------------------------------------------------- template struct ImageBaseSlider::PrivateData { ImageType image; float minimum; float maximum; float step; float value; float valueDef; float valueTmp; bool usingDefault; bool dragging; bool inverted; bool valueIsSet; double startedX; double startedY; Callback* callback; Point startPos; Point endPos; Rectangle sliderArea; PrivateData(const ImageType& img) : image(img), minimum(0.0f), maximum(1.0f), step(0.0f), value(0.5f), valueDef(value), valueTmp(value), usingDefault(false), dragging(false), inverted(false), valueIsSet(false), startedX(0.0), startedY(0.0), callback(nullptr), startPos(), endPos(), sliderArea() {} void recheckArea() noexcept { if (startPos.getY() == endPos.getY()) { // horizontal sliderArea = Rectangle(startPos.getX(), startPos.getY(), endPos.getX() + static_cast(image.getWidth()) - startPos.getX(), static_cast(image.getHeight())); } else { // vertical sliderArea = Rectangle(startPos.getX(), startPos.getY(), static_cast(image.getWidth()), endPos.getY() + static_cast(image.getHeight()) - startPos.getY()); } } DISTRHO_DECLARE_NON_COPYABLE(PrivateData) }; // -------------------------------------------------------------------------------------------------------------------- template ImageBaseSlider::ImageBaseSlider(Widget* const parentWidget, const ImageType& image) noexcept : SubWidget(parentWidget), pData(new PrivateData(image)) { setNeedsFullViewportDrawing(); } template ImageBaseSlider::~ImageBaseSlider() { delete pData; } template float ImageBaseSlider::getValue() const noexcept { return pData->value; } template void ImageBaseSlider::setValue(float value, bool sendCallback) noexcept { if (! pData->valueIsSet) pData->valueIsSet = true; if (d_isEqual(pData->value, value)) return; pData->value = value; if (d_isZero(pData->step)) pData->valueTmp = value; repaint(); if (sendCallback && pData->callback != nullptr) { try { pData->callback->imageSliderValueChanged(this, pData->value); } DISTRHO_SAFE_EXCEPTION("ImageBaseSlider::setValue"); } } template void ImageBaseSlider::setStartPos(const Point& startPos) noexcept { pData->startPos = startPos; pData->recheckArea(); } template void ImageBaseSlider::setStartPos(int x, int y) noexcept { setStartPos(Point(x, y)); } template void ImageBaseSlider::setEndPos(const Point& endPos) noexcept { pData->endPos = endPos; pData->recheckArea(); } template void ImageBaseSlider::setEndPos(int x, int y) noexcept { setEndPos(Point(x, y)); } template void ImageBaseSlider::setInverted(bool inverted) noexcept { if (pData->inverted == inverted) return; pData->inverted = inverted; repaint(); } template void ImageBaseSlider::setDefault(float value) noexcept { pData->valueDef = value; pData->usingDefault = true; } template void ImageBaseSlider::setRange(float min, float max) noexcept { pData->minimum = min; pData->maximum = max; if (pData->value < min) { pData->value = min; repaint(); if (pData->callback != nullptr && pData->valueIsSet) { try { pData->callback->imageSliderValueChanged(this, pData->value); } DISTRHO_SAFE_EXCEPTION("ImageBaseSlider::setRange < min"); } } else if (pData->value > max) { pData->value = max; repaint(); if (pData->callback != nullptr && pData->valueIsSet) { try { pData->callback->imageSliderValueChanged(this, pData->value); } DISTRHO_SAFE_EXCEPTION("ImageBaseSlider::setRange > max"); } } } template void ImageBaseSlider::setStep(float step) noexcept { pData->step = step; } template void ImageBaseSlider::setCallback(Callback* callback) noexcept { pData->callback = callback; } template void ImageBaseSlider::onDisplay() { const GraphicsContext& context(getGraphicsContext()); #if 0 // DEBUG, paints slider area Color(1.0f, 1.0f, 1.0f, 0.5f).setFor(context, true); Rectangle(pData->sliderArea.getX(), pData->sliderArea.getY(), pData->sliderArea.getX()+pData->sliderArea.getWidth(), pData->sliderArea.getY()+pData->sliderArea.getHeight()).draw(context); Color(1.0f, 1.0f, 1.0f, 1.0f).setFor(context, true); #endif const float normValue = (pData->value - pData->minimum) / (pData->maximum - pData->minimum); int x, y; if (pData->startPos.getY() == pData->endPos.getY()) { // horizontal if (pData->inverted) x = pData->endPos.getX() - static_cast(normValue*static_cast(pData->endPos.getX()-pData->startPos.getX())); else x = pData->startPos.getX() + static_cast(normValue*static_cast(pData->endPos.getX()-pData->startPos.getX())); y = pData->startPos.getY(); } else { // vertical x = pData->startPos.getX(); if (pData->inverted) y = pData->endPos.getY() - static_cast(normValue*static_cast(pData->endPos.getY()-pData->startPos.getY())); else y = pData->startPos.getY() + static_cast(normValue*static_cast(pData->endPos.getY()-pData->startPos.getY())); } pData->image.drawAt(context, x, y); } template bool ImageBaseSlider::onMouse(const MouseEvent& ev) { if (ev.button != 1) return false; if (ev.press) { if (! pData->sliderArea.contains(ev.pos)) return false; if ((ev.mod & kModifierShift) != 0 && pData->usingDefault) { setValue(pData->valueDef, true); pData->valueTmp = pData->value; return true; } float vper; const double x = ev.pos.getX(); const double y = ev.pos.getY(); if (pData->startPos.getY() == pData->endPos.getY()) { // horizontal vper = float(x - pData->sliderArea.getX()) / float(pData->sliderArea.getWidth()); } else { // vertical vper = float(y - pData->sliderArea.getY()) / float(pData->sliderArea.getHeight()); } float value; if (pData->inverted) value = pData->maximum - vper * (pData->maximum - pData->minimum); else value = pData->minimum + vper * (pData->maximum - pData->minimum); if (value < pData->minimum) { pData->valueTmp = value = pData->minimum; } else if (value > pData->maximum) { pData->valueTmp = value = pData->maximum; } else if (d_isNotZero(pData->step)) { pData->valueTmp = value; const float rest = std::fmod(value, pData->step); value = value - rest + (rest > pData->step/2.0f ? pData->step : 0.0f); } pData->dragging = true; pData->startedX = x; pData->startedY = y; if (pData->callback != nullptr) pData->callback->imageSliderDragStarted(this); setValue(value, true); return true; } else if (pData->dragging) { if (pData->callback != nullptr) pData->callback->imageSliderDragFinished(this); pData->dragging = false; return true; } return false; } template bool ImageBaseSlider::onMotion(const MotionEvent& ev) { if (! pData->dragging) return false; const bool horizontal = pData->startPos.getY() == pData->endPos.getY(); const double x = ev.pos.getX(); const double y = ev.pos.getY(); if ((horizontal && pData->sliderArea.containsX(x)) || (pData->sliderArea.containsY(y) && ! horizontal)) { float vper; if (horizontal) { // horizontal vper = float(x - pData->sliderArea.getX()) / float(pData->sliderArea.getWidth()); } else { // vertical vper = float(y - pData->sliderArea.getY()) / float(pData->sliderArea.getHeight()); } float value; if (pData->inverted) value = pData->maximum - vper * (pData->maximum - pData->minimum); else value = pData->minimum + vper * (pData->maximum - pData->minimum); if (value < pData->minimum) { pData->valueTmp = value = pData->minimum; } else if (value > pData->maximum) { pData->valueTmp = value = pData->maximum; } else if (d_isNotZero(pData->step)) { pData->valueTmp = value; const float rest = std::fmod(value, pData->step); value = value - rest + (rest > pData->step/2.0f ? pData->step : 0.0f); } setValue(value, true); } else if (horizontal) { if (x < pData->sliderArea.getX()) setValue(pData->inverted ? pData->maximum : pData->minimum, true); else setValue(pData->inverted ? pData->minimum : pData->maximum, true); } else { if (y < pData->sliderArea.getY()) setValue(pData->inverted ? pData->maximum : pData->minimum, true); else setValue(pData->inverted ? pData->minimum : pData->maximum, true); } return true; } // -------------------------------------------------------------------------------------------------------------------- template struct ImageBaseSwitch::PrivateData { ImageType imageNormal; ImageType imageDown; bool isDown; Callback* callback; PrivateData(const ImageType& normal, const ImageType& down) : imageNormal(normal), imageDown(down), isDown(false), callback(nullptr) { DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize()); } PrivateData(PrivateData* const other) : imageNormal(other->imageNormal), imageDown(other->imageDown), isDown(other->isDown), callback(other->callback) { DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize()); } void assignFrom(PrivateData* const other) { imageNormal = other->imageNormal; imageDown = other->imageDown; isDown = other->isDown; callback = other->callback; DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize()); } DISTRHO_DECLARE_NON_COPYABLE(PrivateData) }; // -------------------------------------------------------------------------------------------------------------------- template ImageBaseSwitch::ImageBaseSwitch(Widget* const parentWidget, const ImageType& imageNormal, const ImageType& imageDown) noexcept : SubWidget(parentWidget), pData(new PrivateData(imageNormal, imageDown)) { setSize(imageNormal.getSize()); } template ImageBaseSwitch::ImageBaseSwitch(const ImageBaseSwitch& imageSwitch) noexcept : SubWidget(imageSwitch.getParentWidget()), pData(new PrivateData(imageSwitch.pData)) { setSize(pData->imageNormal.getSize()); } template ImageBaseSwitch& ImageBaseSwitch::operator=(const ImageBaseSwitch& imageSwitch) noexcept { pData->assignFrom(imageSwitch.pData); setSize(pData->imageNormal.getSize()); return *this; } template ImageBaseSwitch::~ImageBaseSwitch() { delete pData; } template bool ImageBaseSwitch::isDown() const noexcept { return pData->isDown; } template void ImageBaseSwitch::setDown(const bool down) noexcept { if (pData->isDown == down) return; pData->isDown = down; repaint(); } template void ImageBaseSwitch::setCallback(Callback* const callback) noexcept { pData->callback = callback; } template void ImageBaseSwitch::onDisplay() { const GraphicsContext& context(getGraphicsContext()); if (pData->isDown) pData->imageDown.draw(context); else pData->imageNormal.draw(context); } template bool ImageBaseSwitch::onMouse(const MouseEvent& ev) { if (ev.press && contains(ev.pos)) { pData->isDown = !pData->isDown; repaint(); if (pData->callback != nullptr) pData->callback->imageSwitchClicked(this, pData->isDown); return true; } return false; } // -------------------------------------------------------------------------------------------------------------------- END_NAMESPACE_DGL