Signed-off-by: falkTX <falktx@falktx.com>pull/292/head
| @@ -54,8 +54,8 @@ public: | |||
| void setCallback(Callback* callback) noexcept; | |||
| bool mouseEvent(const Widget::MouseEvent& ev); | |||
| bool motionEvent(const Widget::MotionEvent& ev); | |||
| bool mouseEvent(const Widget::MouseEvent& ev); | |||
| bool motionEvent(const Widget::MotionEvent& ev); | |||
| protected: | |||
| State getState() const noexcept; | |||
| @@ -63,6 +63,9 @@ protected: | |||
| virtual void stateChanged(State state, State oldState); | |||
| void setInternalCallback(Callback* callback) noexcept; | |||
| void triggerUserCallback(SubWidget* widget, int button); | |||
| private: | |||
| struct PrivateData; | |||
| PrivateData* const pData; | |||
| @@ -72,6 +75,76 @@ private: | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| class KnobEventHandler | |||
| { | |||
| public: | |||
| enum Orientation { | |||
| Horizontal, | |||
| Vertical | |||
| }; | |||
| // NOTE hover not implemented yet | |||
| enum State { | |||
| kKnobStateDefault = 0x0, | |||
| kKnobStateHover = 0x1, | |||
| kKnobStateDragging = 0x2, | |||
| kKnobStateDraggingHover = kKnobStateDragging|kKnobStateHover | |||
| }; | |||
| class Callback | |||
| { | |||
| public: | |||
| virtual ~Callback() {} | |||
| virtual void knobDragStarted(SubWidget* widget) = 0; | |||
| virtual void knobDragFinished(SubWidget* widget) = 0; | |||
| virtual void knobValueChanged(SubWidget* widget, float value) = 0; | |||
| }; | |||
| explicit KnobEventHandler(SubWidget* self); | |||
| explicit KnobEventHandler(SubWidget* self, const KnobEventHandler& other); | |||
| KnobEventHandler& operator=(const KnobEventHandler& other); | |||
| ~KnobEventHandler(); | |||
| // returns raw value, is assumed to be scaled if using log | |||
| float getValue() const noexcept; | |||
| // NOTE: value is assumed to be scaled if using log | |||
| void setValue(float value, bool sendCallback = false) noexcept; | |||
| // returns 0-1 ranged value, already with log scale as needed | |||
| float getNormalizedValue() const noexcept; | |||
| // NOTE: value is assumed to be scaled if using log | |||
| void setDefault(float def) noexcept; | |||
| // NOTE: value is assumed to be scaled if using log | |||
| void setRange(float min, float max) noexcept; | |||
| void setStep(float step) noexcept; | |||
| void setUsingLogScale(bool yesNo) noexcept; | |||
| Orientation getOrientation() const noexcept; | |||
| void setOrientation(const Orientation orientation) noexcept; | |||
| void setCallback(Callback* callback) noexcept; | |||
| bool mouseEvent(const Widget::MouseEvent& ev); | |||
| bool motionEvent(const Widget::MotionEvent& ev); | |||
| bool scrollEvent(const Widget::ScrollEvent& ev); | |||
| protected: | |||
| State getState() const noexcept; | |||
| private: | |||
| struct PrivateData; | |||
| PrivateData* const pData; | |||
| DISTRHO_LEAK_DETECTOR(KnobEventHandler) | |||
| }; | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| END_NAMESPACE_DGL | |||
| #endif // DGL_EVENT_HANDLERS_HPP_INCLUDED | |||
| @@ -82,14 +82,10 @@ private: | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| template <class ImageType> | |||
| class ImageBaseKnob : public SubWidget | |||
| class ImageBaseKnob : public SubWidget, | |||
| public KnobEventHandler | |||
| { | |||
| public: | |||
| enum Orientation { | |||
| Horizontal, | |||
| Vertical | |||
| }; | |||
| class Callback | |||
| { | |||
| public: | |||
| @@ -104,19 +100,9 @@ public: | |||
| ImageBaseKnob& operator=(const ImageBaseKnob& imageKnob); | |||
| ~ImageBaseKnob() override; | |||
| float getValue() const noexcept; | |||
| void setDefault(float def) noexcept; | |||
| void setRange(float min, float max) noexcept; | |||
| void setStep(float step) noexcept; | |||
| void setValue(float value, bool sendCallback = false) noexcept; | |||
| void setUsingLogScale(bool yesNo) noexcept; | |||
| void setCallback(Callback* callback) noexcept; | |||
| void setOrientation(Orientation orientation) noexcept; | |||
| void setRotationAngle(int angle); | |||
| void setImageLayerCount(uint count) noexcept; | |||
| void setRotationAngle(int angle); | |||
| protected: | |||
| void onDisplay() override; | |||
| @@ -24,7 +24,6 @@ | |||
| #include "../Color.hpp" | |||
| #include "../ImageBaseWidgets.hpp" | |||
| #include "Common.hpp" | |||
| #include "SubWidgetPrivateData.hpp" | |||
| #include "TopLevelWidgetPrivateData.hpp" | |||
| #include "WidgetPrivateData.hpp" | |||
| @@ -657,8 +656,7 @@ void ImageBaseKnob<CairoImage>::onDisplay() | |||
| { | |||
| const GraphicsContext& context(getGraphicsContext()); | |||
| cairo_t* const handle = ((const CairoGraphicsContext&)context).handle; | |||
| const double normValue = ((pData->usingLog ? pData->invlogscale(pData->value) : pData->value) - pData->minimum) | |||
| / (pData->maximum - pData->minimum); | |||
| const double normValue = getNormalizedValue(); | |||
| cairo_surface_t* surface = (cairo_surface_t*)pData->cairoSurface; | |||
| @@ -1,91 +0,0 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * 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. | |||
| */ | |||
| #ifndef DGL_COMMON_HPP_INCLUDED | |||
| #define DGL_COMMON_HPP_INCLUDED | |||
| #include "../ImageBaseWidgets.hpp" | |||
| START_NAMESPACE_DGL | |||
| // ----------------------------------------------------------------------- | |||
| template <class ImageType> | |||
| struct ImageBaseKnob<ImageType>::PrivateData { | |||
| ImageType image; | |||
| float minimum; | |||
| float maximum; | |||
| float step; | |||
| float value; | |||
| float valueDef; | |||
| float valueTmp; | |||
| bool usingDefault; | |||
| bool usingLog; | |||
| Orientation orientation; | |||
| int rotationAngle; | |||
| bool dragging; | |||
| double lastX; | |||
| double lastY; | |||
| Callback* callback; | |||
| bool alwaysRepaint; | |||
| bool isImgVertical; | |||
| uint imgLayerWidth; | |||
| uint imgLayerHeight; | |||
| uint imgLayerCount; | |||
| bool isReady; | |||
| union { | |||
| uint glTextureId; | |||
| void* cairoSurface; | |||
| }; | |||
| explicit PrivateData(const ImageType& img, const Orientation o); | |||
| explicit PrivateData(PrivateData* const other); | |||
| void assignFrom(PrivateData* const other); | |||
| ~PrivateData() | |||
| { | |||
| cleanup(); | |||
| } | |||
| void init(); | |||
| void cleanup(); | |||
| inline float logscale(const float v) const | |||
| { | |||
| const float b = std::log(maximum/minimum)/(maximum-minimum); | |||
| const float a = maximum/std::exp(maximum*b); | |||
| return a * std::exp(b*v); | |||
| } | |||
| inline float invlogscale(const float v) const | |||
| { | |||
| const float b = std::log(maximum/minimum)/(maximum-minimum); | |||
| const float a = maximum/std::exp(maximum*b); | |||
| return std::log(v/a)/b; | |||
| } | |||
| DISTRHO_DECLARE_NON_COPYABLE(PrivateData) | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| END_NAMESPACE_DGL | |||
| #endif // DGL_APP_PRIVATE_DATA_HPP_INCLUDED | |||
| @@ -24,7 +24,8 @@ START_NAMESPACE_DGL | |||
| struct ButtonEventHandler::PrivateData { | |||
| ButtonEventHandler* const self; | |||
| SubWidget* const widget; | |||
| ButtonEventHandler::Callback* callback; | |||
| ButtonEventHandler::Callback* internalCallback; | |||
| ButtonEventHandler::Callback* userCallback; | |||
| int button; | |||
| int state; | |||
| @@ -36,7 +37,8 @@ struct ButtonEventHandler::PrivateData { | |||
| PrivateData(ButtonEventHandler* const s, SubWidget* const w) | |||
| : self(s), | |||
| widget(w), | |||
| callback(nullptr), | |||
| internalCallback(nullptr), | |||
| userCallback(nullptr), | |||
| button(-1), | |||
| state(kButtonStateDefault), | |||
| checkable(false), | |||
| @@ -72,8 +74,10 @@ struct ButtonEventHandler::PrivateData { | |||
| if (checkable) | |||
| checked = !checked; | |||
| if (callback != nullptr) | |||
| callback->buttonClicked(widget, button2); | |||
| if (internalCallback != nullptr) | |||
| internalCallback->buttonClicked(widget, button2); | |||
| else if (userCallback != nullptr) | |||
| userCallback->buttonClicked(widget, button2); | |||
| return true; | |||
| } | |||
| @@ -141,8 +145,13 @@ struct ButtonEventHandler::PrivateData { | |||
| state |= kButtonStateActive; | |||
| widget->repaint(); | |||
| if (sendCallback && callback != nullptr) | |||
| callback->buttonClicked(widget, -1); | |||
| if (sendCallback) | |||
| { | |||
| if (internalCallback != nullptr) | |||
| internalCallback->buttonClicked(widget, -1); | |||
| else if (userCallback != nullptr) | |||
| userCallback->buttonClicked(widget, -1); | |||
| } | |||
| } | |||
| void setChecked(const bool checked2, const bool sendCallback) noexcept | |||
| @@ -153,8 +162,13 @@ struct ButtonEventHandler::PrivateData { | |||
| checked = checked2; | |||
| widget->repaint(); | |||
| if (sendCallback && callback != nullptr) | |||
| callback->buttonClicked(widget, -1); | |||
| if (sendCallback) | |||
| { | |||
| if (internalCallback != nullptr) | |||
| internalCallback->buttonClicked(widget, -1); | |||
| else if (userCallback != nullptr) | |||
| userCallback->buttonClicked(widget, -1); | |||
| } | |||
| } | |||
| DISTRHO_DECLARE_NON_COPYABLE(PrivateData) | |||
| @@ -205,7 +219,7 @@ void ButtonEventHandler::setCheckable(const bool checkable) noexcept | |||
| void ButtonEventHandler::setCallback(Callback* const callback) noexcept | |||
| { | |||
| pData->callback = callback; | |||
| pData->userCallback = callback; | |||
| } | |||
| bool ButtonEventHandler::mouseEvent(const Widget::MouseEvent& ev) | |||
| @@ -232,6 +246,375 @@ void ButtonEventHandler::stateChanged(State, State) | |||
| { | |||
| } | |||
| void ButtonEventHandler::setInternalCallback(Callback* const callback) noexcept | |||
| { | |||
| pData->internalCallback = callback; | |||
| } | |||
| void ButtonEventHandler::triggerUserCallback(SubWidget* const widget, const int button) | |||
| { | |||
| if (pData->userCallback != nullptr) | |||
| pData->userCallback->buttonClicked(widget, button); | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| struct KnobEventHandler::PrivateData { | |||
| KnobEventHandler* const self; | |||
| SubWidget* const widget; | |||
| KnobEventHandler::Callback* callback; | |||
| float minimum; | |||
| float maximum; | |||
| float step; | |||
| float value; | |||
| float valueDef; | |||
| float valueTmp; | |||
| bool usingDefault; | |||
| bool usingLog; | |||
| Orientation orientation; | |||
| int state; | |||
| double lastX; | |||
| double lastY; | |||
| PrivateData(KnobEventHandler* const s, SubWidget* const w) | |||
| : self(s), | |||
| widget(w), | |||
| callback(nullptr), | |||
| minimum(0.0f), | |||
| maximum(1.0f), | |||
| step(0.0f), | |||
| value(0.5f), | |||
| valueDef(value), | |||
| valueTmp(value), | |||
| usingDefault(false), | |||
| usingLog(false), | |||
| orientation(Horizontal), | |||
| state(kKnobStateDefault), | |||
| lastX(0.0), | |||
| lastY(0.0) {} | |||
| PrivateData(KnobEventHandler* const s, SubWidget* const w, PrivateData* const other) | |||
| : self(s), | |||
| widget(w), | |||
| callback(other->callback), | |||
| 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), | |||
| state(kKnobStateDefault), | |||
| lastX(0.0), | |||
| lastY(0.0) {} | |||
| void assignFrom(PrivateData* const other) | |||
| { | |||
| callback = other->callback; | |||
| 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; | |||
| state = kKnobStateDefault; | |||
| lastX = 0.0; | |||
| lastY = 0.0; | |||
| } | |||
| inline float logscale(const float v) const | |||
| { | |||
| const float b = std::log(maximum/minimum)/(maximum-minimum); | |||
| const float a = maximum/std::exp(maximum*b); | |||
| return a * std::exp(b*v); | |||
| } | |||
| inline float invlogscale(const float v) const | |||
| { | |||
| const float b = std::log(maximum/minimum)/(maximum-minimum); | |||
| const float a = maximum/std::exp(maximum*b); | |||
| return std::log(v/a)/b; | |||
| } | |||
| bool mouseEvent(const Widget::MouseEvent& ev) | |||
| { | |||
| if (ev.button != 1) | |||
| return false; | |||
| if (ev.press) | |||
| { | |||
| if (! widget->contains(ev.pos)) | |||
| return false; | |||
| if ((ev.mod & kModifierShift) != 0 && usingDefault) | |||
| { | |||
| setValue(valueDef, true); | |||
| valueTmp = value; | |||
| return true; | |||
| } | |||
| state |= kKnobStateDragging; | |||
| lastX = ev.pos.getX(); | |||
| lastY = ev.pos.getY(); | |||
| widget->repaint(); | |||
| if (callback != nullptr) | |||
| callback->knobDragStarted(widget); | |||
| return true; | |||
| } | |||
| else if (state & kKnobStateDragging) | |||
| { | |||
| state &= ~kKnobStateDragging; | |||
| widget->repaint(); | |||
| if (callback != nullptr) | |||
| callback->knobDragFinished(widget); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| bool motionEvent(const Widget::MotionEvent& ev) | |||
| { | |||
| if ((state & kKnobStateDragging) == 0x0) | |||
| return false; | |||
| bool doVal = false; | |||
| float d, value2 = 0.0f; | |||
| if (orientation == Horizontal) | |||
| { | |||
| if (const double movX = ev.pos.getX() - lastX) | |||
| { | |||
| d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; | |||
| value2 = (usingLog ? invlogscale(valueTmp) : valueTmp) + (float(maximum - minimum) / d * float(movX)); | |||
| doVal = true; | |||
| } | |||
| } | |||
| else if (orientation == Vertical) | |||
| { | |||
| if (const double movY = lastY - ev.pos.getY()) | |||
| { | |||
| d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; | |||
| value2 = (usingLog ? invlogscale(valueTmp) : valueTmp) + (float(maximum - minimum) / d * float(movY)); | |||
| doVal = true; | |||
| } | |||
| } | |||
| if (! doVal) | |||
| return false; | |||
| if (usingLog) | |||
| value2 = logscale(value2); | |||
| if (value2 < minimum) | |||
| { | |||
| valueTmp = value2 = minimum; | |||
| } | |||
| else if (value2 > maximum) | |||
| { | |||
| valueTmp = value2 = maximum; | |||
| } | |||
| else | |||
| { | |||
| valueTmp = value2; | |||
| if (d_isNotZero(step)) | |||
| { | |||
| const float rest = std::fmod(value2, step); | |||
| value2 -= rest + (rest > step/2.0f ? step : 0.0f); | |||
| } | |||
| } | |||
| setValue(value2, true); | |||
| lastX = ev.pos.getX(); | |||
| lastY = ev.pos.getY(); | |||
| return true; | |||
| } | |||
| bool scrollEvent(const Widget::ScrollEvent& ev) | |||
| { | |||
| if (! widget->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 value2 = (usingLog ? invlogscale(valueTmp) : valueTmp) | |||
| + ((maximum - minimum) / d * 10.f * dir); | |||
| if (usingLog) | |||
| value2 = logscale(value2); | |||
| if (value2 < minimum) | |||
| { | |||
| valueTmp = value2 = minimum; | |||
| } | |||
| else if (value2 > maximum) | |||
| { | |||
| valueTmp = value2 = maximum; | |||
| } | |||
| else | |||
| { | |||
| valueTmp = value2; | |||
| if (d_isNotZero(step)) | |||
| { | |||
| const float rest = std::fmod(value2, step); | |||
| value2 = value2 - rest + (rest > step/2.0f ? step : 0.0f); | |||
| } | |||
| } | |||
| setValue(value2, true); | |||
| return true; | |||
| } | |||
| float getNormalizedValue() const noexcept | |||
| { | |||
| const float diff = maximum - minimum; | |||
| return ((usingLog ? invlogscale(value) : value) - minimum) / diff; | |||
| } | |||
| void setRange(const float min, const float max) noexcept | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(max > min,); | |||
| if (value < min) | |||
| { | |||
| valueTmp = value = min; | |||
| widget->repaint(); | |||
| } | |||
| else if (value > max) | |||
| { | |||
| valueTmp = value = max; | |||
| widget->repaint(); | |||
| } | |||
| minimum = min; | |||
| maximum = max; | |||
| } | |||
| void setValue(const float value2, const bool sendCallback) | |||
| { | |||
| if (d_isEqual(value, value2)) | |||
| return; | |||
| valueTmp = value = value2; | |||
| widget->repaint(); | |||
| if (sendCallback && callback != nullptr) | |||
| { | |||
| try { | |||
| callback->knobValueChanged(widget, value); | |||
| } DISTRHO_SAFE_EXCEPTION("KnobEventHandler::setValue"); | |||
| } | |||
| } | |||
| }; | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| KnobEventHandler::KnobEventHandler(SubWidget* const self) | |||
| : pData(new PrivateData(this, self)) {} | |||
| KnobEventHandler::KnobEventHandler(SubWidget* const self, const KnobEventHandler& other) | |||
| : pData(new PrivateData(this, self, other.pData)) {} | |||
| KnobEventHandler& KnobEventHandler::operator=(const KnobEventHandler& other) | |||
| { | |||
| pData->assignFrom(other.pData); | |||
| return *this; | |||
| } | |||
| KnobEventHandler::~KnobEventHandler() | |||
| { | |||
| delete pData; | |||
| } | |||
| float KnobEventHandler::getValue() const noexcept | |||
| { | |||
| return pData->value; | |||
| } | |||
| void KnobEventHandler::setValue(const float value, const bool sendCallback) noexcept | |||
| { | |||
| pData->setValue(value, sendCallback); | |||
| } | |||
| float KnobEventHandler::getNormalizedValue() const noexcept | |||
| { | |||
| return pData->getNormalizedValue(); | |||
| } | |||
| void KnobEventHandler::setDefault(const float def) noexcept | |||
| { | |||
| pData->valueDef = def; | |||
| pData->usingDefault = true; | |||
| } | |||
| void KnobEventHandler::setRange(const float min, const float max) noexcept | |||
| { | |||
| pData->setRange(min, max); | |||
| } | |||
| void KnobEventHandler::setStep(const float step) noexcept | |||
| { | |||
| pData->step = step; | |||
| } | |||
| void KnobEventHandler::setUsingLogScale(const bool yesNo) noexcept | |||
| { | |||
| pData->usingLog = yesNo; | |||
| } | |||
| KnobEventHandler::Orientation KnobEventHandler::getOrientation() const noexcept | |||
| { | |||
| return pData->orientation; | |||
| } | |||
| void KnobEventHandler::setOrientation(const Orientation orientation) noexcept | |||
| { | |||
| if (pData->orientation == orientation) | |||
| return; | |||
| pData->orientation = orientation; | |||
| } | |||
| void KnobEventHandler::setCallback(Callback* const callback) noexcept | |||
| { | |||
| pData->callback = callback; | |||
| } | |||
| bool KnobEventHandler::mouseEvent(const Widget::MouseEvent& ev) | |||
| { | |||
| return pData->mouseEvent(ev); | |||
| } | |||
| bool KnobEventHandler::motionEvent(const Widget::MotionEvent& ev) | |||
| { | |||
| return pData->motionEvent(ev); | |||
| } | |||
| bool KnobEventHandler::scrollEvent(const Widget::ScrollEvent& ev) | |||
| { | |||
| return pData->scrollEvent(ev); | |||
| } | |||
| KnobEventHandler::State KnobEventHandler::getState() const noexcept | |||
| { | |||
| return static_cast<State>(pData->state); | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| END_NAMESPACE_DGL | |||
| @@ -16,7 +16,6 @@ | |||
| #include "../ImageBaseWidgets.hpp" | |||
| #include "../Color.hpp" | |||
| #include "Common.hpp" | |||
| START_NAMESPACE_DGL | |||
| @@ -193,108 +192,131 @@ bool ImageBaseButton<ImageType>::onMotion(const MotionEvent& ev) | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| template <class ImageType> | |||
| ImageBaseKnob<ImageType>::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(); | |||
| } | |||
| struct ImageBaseKnob<ImageType>::PrivateData : public KnobEventHandler::Callback { | |||
| ImageBaseKnob<ImageType>::Callback* callback; | |||
| ImageType image; | |||
| template <class ImageType> | |||
| ImageBaseKnob<ImageType>::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(); | |||
| } | |||
| int rotationAngle; | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::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(); | |||
| } | |||
| bool alwaysRepaint; | |||
| bool isImgVertical; | |||
| uint imgLayerWidth; | |||
| uint imgLayerHeight; | |||
| uint imgLayerCount; | |||
| bool isReady; | |||
| union { | |||
| uint glTextureId; | |||
| void* cairoSurface; | |||
| }; | |||
| explicit PrivateData(const ImageType& img) | |||
| : callback(nullptr), | |||
| image(img), | |||
| rotationAngle(0), | |||
| 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(); | |||
| } | |||
| explicit PrivateData(PrivateData* const other) | |||
| : callback(other->callback), | |||
| image(other->image), | |||
| rotationAngle(other->rotationAngle), | |||
| alwaysRepaint(other->alwaysRepaint), | |||
| isImgVertical(other->isImgVertical), | |||
| imgLayerWidth(other->imgLayerWidth), | |||
| imgLayerHeight(other->imgLayerHeight), | |||
| imgLayerCount(other->imgLayerCount), | |||
| isReady(false) | |||
| { | |||
| init(); | |||
| } | |||
| void assignFrom(PrivateData* const other) | |||
| { | |||
| cleanup(); | |||
| image = other->image; | |||
| rotationAngle = other->rotationAngle; | |||
| callback = other->callback; | |||
| alwaysRepaint = other->alwaysRepaint; | |||
| isImgVertical = other->isImgVertical; | |||
| imgLayerWidth = other->imgLayerWidth; | |||
| imgLayerHeight = other->imgLayerHeight; | |||
| imgLayerCount = other->imgLayerCount; | |||
| isReady = false; | |||
| init(); | |||
| } | |||
| ~PrivateData() | |||
| { | |||
| cleanup(); | |||
| } | |||
| void knobDragStarted(SubWidget* const widget) override | |||
| { | |||
| if (callback != nullptr) | |||
| if (ImageBaseKnob* const imageKnob = dynamic_cast<ImageBaseKnob*>(widget)) | |||
| callback->imageKnobDragStarted(imageKnob); | |||
| } | |||
| void knobDragFinished(SubWidget* const widget) override | |||
| { | |||
| if (callback != nullptr) | |||
| if (ImageBaseKnob* const imageKnob = dynamic_cast<ImageBaseKnob*>(widget)) | |||
| callback->imageKnobDragFinished(imageKnob); | |||
| } | |||
| void knobValueChanged(SubWidget* const widget, const float value) override | |||
| { | |||
| if (rotationAngle == 0 || alwaysRepaint) | |||
| isReady = false; | |||
| if (callback != nullptr) | |||
| if (ImageBaseKnob* const imageKnob = dynamic_cast<ImageBaseKnob*>(widget)) | |||
| callback->imageKnobValueChanged(imageKnob, value); | |||
| } | |||
| // implemented independently per graphics backend | |||
| void init(); | |||
| void cleanup(); | |||
| DISTRHO_DECLARE_NON_COPYABLE(PrivateData) | |||
| }; | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| template <class ImageType> | |||
| ImageBaseKnob<ImageType>::ImageBaseKnob(Widget* const parentWidget, const ImageType& image, const Orientation orientation) noexcept | |||
| ImageBaseKnob<ImageType>::ImageBaseKnob(Widget* const parentWidget, | |||
| const ImageType& image, | |||
| const Orientation orientation) noexcept | |||
| : SubWidget(parentWidget), | |||
| pData(new PrivateData(image, orientation)) | |||
| KnobEventHandler(this), | |||
| pData(new PrivateData(image)) | |||
| { | |||
| setOrientation(orientation); | |||
| setSize(pData->imgLayerWidth, pData->imgLayerHeight); | |||
| } | |||
| template <class ImageType> | |||
| ImageBaseKnob<ImageType>::ImageBaseKnob(const ImageBaseKnob<ImageType>& imageKnob) | |||
| : SubWidget(imageKnob.getParentWidget()), | |||
| KnobEventHandler(this, imageKnob), | |||
| pData(new PrivateData(imageKnob.pData)) | |||
| { | |||
| setOrientation(imageKnob.getOrientation()); | |||
| setSize(pData->imgLayerWidth, pData->imgLayerHeight); | |||
| } | |||
| template <class ImageType> | |||
| ImageBaseKnob<ImageType>& ImageBaseKnob<ImageType>::operator=(const ImageBaseKnob<ImageType>& imageKnob) | |||
| { | |||
| KnobEventHandler::operator=(imageKnob); | |||
| pData->assignFrom(imageKnob.pData); | |||
| setSize(pData->imgLayerWidth, pData->imgLayerHeight); | |||
| return *this; | |||
| @@ -306,116 +328,12 @@ ImageBaseKnob<ImageType>::~ImageBaseKnob() | |||
| delete pData; | |||
| } | |||
| template <class ImageType> | |||
| float ImageBaseKnob<ImageType>::getValue() const noexcept | |||
| { | |||
| return pData->value; | |||
| } | |||
| // NOTE: value is assumed to be scaled if using log | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::setDefault(float value) noexcept | |||
| { | |||
| pData->valueDef = value; | |||
| pData->usingDefault = true; | |||
| } | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::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<ImageType>::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<ImageType>::setRange > max"); | |||
| } | |||
| } | |||
| pData->minimum = min; | |||
| pData->maximum = max; | |||
| } | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::setStep(float step) noexcept | |||
| { | |||
| pData->step = step; | |||
| } | |||
| // NOTE: value is assumed to be scaled if using log | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::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<ImageType>::setValue"); | |||
| } | |||
| } | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::setUsingLogScale(bool yesNo) noexcept | |||
| { | |||
| pData->usingLog = yesNo; | |||
| } | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::setCallback(Callback* callback) noexcept | |||
| { | |||
| pData->callback = callback; | |||
| } | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::setOrientation(Orientation orientation) noexcept | |||
| { | |||
| if (pData->orientation == orientation) | |||
| return; | |||
| pData->orientation = orientation; | |||
| } | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::setRotationAngle(int angle) | |||
| { | |||
| if (pData->rotationAngle == angle) | |||
| return; | |||
| pData->rotationAngle = angle; | |||
| pData->isReady = false; | |||
| } | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::setImageLayerCount(uint count) noexcept | |||
| { | |||
| @@ -432,132 +350,37 @@ void ImageBaseKnob<ImageType>::setImageLayerCount(uint count) noexcept | |||
| } | |||
| template <class ImageType> | |||
| bool ImageBaseKnob<ImageType>::onMouse(const MouseEvent& ev) | |||
| void ImageBaseKnob<ImageType>::setRotationAngle(int angle) | |||
| { | |||
| 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); | |||
| if (pData->rotationAngle == angle) | |||
| return; | |||
| return true; | |||
| } | |||
| else if (pData->dragging) | |||
| { | |||
| if (pData->callback != nullptr) | |||
| pData->callback->imageKnobDragFinished(this); | |||
| pData->rotationAngle = angle; | |||
| pData->isReady = false; | |||
| } | |||
| pData->dragging = false; | |||
| template <class ImageType> | |||
| bool ImageBaseKnob<ImageType>::onMouse(const MouseEvent& ev) | |||
| { | |||
| if (SubWidget::onMouse(ev)) | |||
| return true; | |||
| } | |||
| return false; | |||
| return KnobEventHandler::mouseEvent(ev); | |||
| } | |||
| template <class ImageType> | |||
| bool ImageBaseKnob<ImageType>::onMotion(const MotionEvent& ev) | |||
| { | |||
| if (! pData->dragging) | |||
| return false; | |||
| bool doVal = false; | |||
| float d, value = 0.0f; | |||
| if (pData->orientation == ImageBaseKnob<ImageType>::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<ImageType>::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; | |||
| if (SubWidget::onMotion(ev)) | |||
| return true; | |||
| return KnobEventHandler::motionEvent(ev); | |||
| } | |||
| template <class ImageType> | |||
| bool ImageBaseKnob<ImageType>::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; | |||
| if (SubWidget::onScroll(ev)) | |||
| return true; | |||
| return KnobEventHandler::scrollEvent(ev); | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| @@ -23,7 +23,6 @@ | |||
| #include "../Color.hpp" | |||
| #include "../ImageWidgets.hpp" | |||
| #include "Common.hpp" | |||
| #include "SubWidgetPrivateData.hpp" | |||
| #include "TopLevelWidgetPrivateData.hpp" | |||
| #include "WidgetPrivateData.hpp" | |||
| @@ -486,8 +485,7 @@ template <> | |||
| void ImageBaseKnob<OpenGLImage>::onDisplay() | |||
| { | |||
| const GraphicsContext& context(getGraphicsContext()); | |||
| const float normValue = ((pData->usingLog ? pData->invlogscale(pData->value) : pData->value) - pData->minimum) | |||
| / (pData->maximum - pData->minimum); | |||
| const float normValue = getNormalizedValue(); | |||
| glEnable(GL_TEXTURE_2D); | |||
| glBindTexture(GL_TEXTURE_2D, pData->glTextureId); | |||