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); | |||