From e371bc62203710a168b3a088777f8a0dfcaab36e Mon Sep 17 00:00:00 2001 From: Damien Zammit Date: Sun, 5 Jun 2016 11:56:59 +1000 Subject: Add numeric labels to plugins with break-out widget Signed-off-by: Damien Zammit --- widgets/ZamWidgets.hpp | 631 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 631 insertions(+) create mode 100644 widgets/ZamWidgets.hpp (limited to 'widgets') diff --git a/widgets/ZamWidgets.hpp b/widgets/ZamWidgets.hpp new file mode 100644 index 0000000..bf09a93 --- /dev/null +++ b/widgets/ZamWidgets.hpp @@ -0,0 +1,631 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2016 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. + */ + +// This is a custom knob widget for DPF with numeric label for zam-plugins + +#ifndef DGL_ZAM_WIDGETS_HPP_INCLUDED +#define DGL_ZAM_WIDGETS_HPP_INCLUDED + +#include "../dpf/dgl/Image.hpp" +#include "../dpf/dgl/Widget.hpp" +#include "../dpf/dgl/Window.hpp" +#include "../dpf/dgl/NanoVG.hpp" +#include "../dpf/dgl/src/Common.hpp" +#include "../dpf/dgl/src/WidgetPrivateData.hpp" + +START_NAMESPACE_DGL + +// ----------------------------------------------------------------------- + +class ZamKnob : public Widget, + public NanoVG +{ +public: + enum Orientation { + Horizontal, + Vertical + }; + + class Callback + { + public: + virtual ~Callback() {} + virtual void imageKnobDragStarted(ZamKnob* imageKnob) = 0; + virtual void imageKnobDragFinished(ZamKnob* imageKnob) = 0; + virtual void imageKnobValueChanged(ZamKnob* imageKnob, float value) = 0; + }; + + explicit ZamKnob(Window& parent, const Image& image, Orientation orientation = Vertical) noexcept; + explicit ZamKnob(Widget* widget, const Image& image, Orientation orientation = Vertical) noexcept; + explicit ZamKnob(const ZamKnob& imageKnob); + ZamKnob& operator=(const ZamKnob& imageKnob); + ~ZamKnob() override; + + float getValue() const noexcept; + + void setDefault(float def) noexcept; + void setRange(float min, float max) noexcept; + void setStep(float step) noexcept; + void setScrollStep(float step) noexcept; + void setLabel(bool label) 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; + +protected: + void onDisplay() override; + bool onMouse(const MouseEvent&) override; + bool onMotion(const MotionEvent&) override; + bool onScroll(const ScrollEvent&) override; + +private: + Image fImage; + bool fLabel; + float fMinimum; + float fMaximum; + float fScrollStep; + float fStep; + float fValue; + float fValueDef; + float fValueTmp; + bool fUsingDefault; + bool fUsingLog; + Orientation fOrientation; + + int fRotationAngle; + bool fDragging; + int fLastX; + int fLastY; + + Callback* fCallback; + + bool fIsImgVertical; + uint fImgLayerWidth; + uint fImgLayerHeight; + uint fImgLayerCount; + bool fIsReady; + GLuint fTextureId; + + float _logscale(float value) const; + float _invlogscale(float value) const; + void labelDisplay(void); + + DISTRHO_LEAK_DETECTOR(ZamKnob) +}; + +// ----------------------------------------------------------------------- + +ZamKnob::ZamKnob(Window& parent, const Image& image, Orientation orientation) noexcept + : Widget(parent), + NanoVG(CREATE_ANTIALIAS), + fLabel(false), + fImage(image), + fMinimum(0.0f), + fMaximum(1.0f), + fScrollStep(0.0f), + fStep(0.0f), + fValue(0.5f), + fValueDef(fValue), + fValueTmp(fValue), + fUsingDefault(false), + fUsingLog(false), + fOrientation(orientation), + fRotationAngle(0), + fDragging(false), + fLastX(0), + fLastY(0), + fCallback(nullptr), + fIsImgVertical(image.getHeight() > image.getWidth()), + fImgLayerWidth(fIsImgVertical ? image.getWidth() : image.getHeight()), + fImgLayerHeight(fImgLayerWidth), + fImgLayerCount(fIsImgVertical ? image.getHeight()/fImgLayerHeight : image.getWidth()/fImgLayerWidth), + fIsReady(false), + fTextureId(0) +{ + glGenTextures(1, &fTextureId); + setSize(fImgLayerWidth, fImgLayerHeight); + NanoVG::loadSharedResources(); +} + +ZamKnob::ZamKnob(Widget* widget, const Image& image, Orientation orientation) noexcept + : Widget(widget->getParentWindow()), + NanoVG(CREATE_ANTIALIAS), + fImage(image), + fLabel(false), + fMinimum(0.0f), + fMaximum(1.0f), + fScrollStep(0.0f), + fStep(0.0f), + fValue(0.5f), + fValueDef(fValue), + fValueTmp(fValue), + fUsingDefault(false), + fUsingLog(false), + fOrientation(orientation), + fRotationAngle(0), + fDragging(false), + fLastX(0), + fLastY(0), + fCallback(nullptr), + fIsImgVertical(image.getHeight() > image.getWidth()), + fImgLayerWidth(fIsImgVertical ? image.getWidth() : image.getHeight()), + fImgLayerHeight(fImgLayerWidth), + fImgLayerCount(fIsImgVertical ? image.getHeight()/fImgLayerHeight : image.getWidth()/fImgLayerWidth), + fIsReady(false), + fTextureId(0) +{ + glGenTextures(1, &fTextureId); + setSize(fImgLayerWidth, fImgLayerHeight); + NanoVG::loadSharedResources(); +} + +ZamKnob::ZamKnob(const ZamKnob& imageKnob) + : Widget(imageKnob.getParentWindow()), + NanoVG(CREATE_ANTIALIAS), + fImage(imageKnob.fImage), + fLabel(false), + fMinimum(imageKnob.fMinimum), + fMaximum(imageKnob.fMaximum), + fScrollStep(imageKnob.fScrollStep), + fStep(imageKnob.fStep), + fValue(imageKnob.fValue), + fValueDef(imageKnob.fValueDef), + fValueTmp(fValue), + fUsingDefault(imageKnob.fUsingDefault), + fUsingLog(imageKnob.fUsingLog), + fOrientation(imageKnob.fOrientation), + fRotationAngle(imageKnob.fRotationAngle), + fDragging(false), + fLastX(0), + fLastY(0), + fCallback(imageKnob.fCallback), + fIsImgVertical(imageKnob.fIsImgVertical), + fImgLayerWidth(imageKnob.fImgLayerWidth), + fImgLayerHeight(imageKnob.fImgLayerHeight), + fImgLayerCount(imageKnob.fImgLayerCount), + fIsReady(false), + fTextureId(0) +{ + glGenTextures(1, &fTextureId); + setSize(fImgLayerWidth, fImgLayerHeight); + NanoVG::loadSharedResources(); +} + +ZamKnob& ZamKnob::operator=(const ZamKnob& imageKnob) +{ + fImage = imageKnob.fImage; + fMinimum = imageKnob.fMinimum; + fMaximum = imageKnob.fMaximum; + fScrollStep = imageKnob.fScrollStep; + fStep = imageKnob.fStep; + fValue = imageKnob.fValue; + fValueDef = imageKnob.fValueDef; + fValueTmp = fValue; + fUsingDefault = imageKnob.fUsingDefault; + fUsingLog = imageKnob.fUsingLog; + fOrientation = imageKnob.fOrientation; + fRotationAngle = imageKnob.fRotationAngle; + fDragging = false; + fLastX = 0; + fLastY = 0; + fCallback = imageKnob.fCallback; + fIsImgVertical = imageKnob.fIsImgVertical; + fImgLayerWidth = imageKnob.fImgLayerWidth; + fImgLayerHeight = imageKnob.fImgLayerHeight; + fImgLayerCount = imageKnob.fImgLayerCount; + fIsReady = false; + + if (fTextureId != 0) + { + glDeleteTextures(1, &fTextureId); + fTextureId = 0; + } + + glGenTextures(1, &fTextureId); + setSize(fImgLayerWidth, fImgLayerHeight); + + return *this; +} + +ZamKnob::~ZamKnob() +{ + if (fTextureId != 0) + { + glDeleteTextures(1, &fTextureId); + fTextureId = 0; + } +} + +float ZamKnob::getValue() const noexcept +{ + return fValue; +} + +// NOTE: value is assumed to be scaled if using log +void ZamKnob::setDefault(float value) noexcept +{ + fValueDef = value; + fUsingDefault = true; +} + +void ZamKnob::setRange(float min, float max) noexcept +{ + DISTRHO_SAFE_ASSERT_RETURN(max > min,); + + if (fValue < min) + { + fValue = min; + repaint(); + + if (fCallback != nullptr) + { + try { + fCallback->imageKnobValueChanged(this, fValue); + } DISTRHO_SAFE_EXCEPTION("ZamKnob::setRange < min"); + } + } + else if (fValue > max) + { + fValue = max; + repaint(); + + if (fCallback != nullptr) + { + try { + fCallback->imageKnobValueChanged(this, fValue); + } DISTRHO_SAFE_EXCEPTION("ZamKnob::setRange > max"); + } + } + + fMinimum = min; + fMaximum = max; +} + +void ZamKnob::setScrollStep(float step) noexcept +{ + fScrollStep = step; +} + +void ZamKnob::setLabel(bool label) noexcept +{ + fLabel = label; +} + +void ZamKnob::setStep(float step) noexcept +{ + fStep = step; +} + +// NOTE: value is assumed to be scaled if using log +void ZamKnob::setValue(float value, bool sendCallback) noexcept +{ + if (d_isEqual(fValue, value)) + return; + + fValue = value; + + if (d_isZero(fStep)) + fValueTmp = value; + + if (fRotationAngle == 0) + fIsReady = false; + + repaint(); + + if (sendCallback && fCallback != nullptr) + { + try { + fCallback->imageKnobValueChanged(this, fValue); + } DISTRHO_SAFE_EXCEPTION("ZamKnob::setValue"); + } +} + +void ZamKnob::setUsingLogScale(bool yesNo) noexcept +{ + fUsingLog = yesNo; +} + +void ZamKnob::setCallback(Callback* callback) noexcept +{ + fCallback = callback; +} + +void ZamKnob::setOrientation(Orientation orientation) noexcept +{ + if (fOrientation == orientation) + return; + + fOrientation = orientation; +} + +void ZamKnob::setRotationAngle(int angle) +{ + if (fRotationAngle == angle) + return; + + fRotationAngle = angle; + fIsReady = false; +} + +void ZamKnob::setImageLayerCount(uint count) noexcept +{ + DISTRHO_SAFE_ASSERT_RETURN(count > 1,); + + fImgLayerCount = count; + + if (fIsImgVertical) + fImgLayerHeight = fImage.getHeight()/count; + else + fImgLayerWidth = fImage.getWidth()/count; + + setSize(fImgLayerWidth, fImgLayerHeight); +} + +void ZamKnob::labelDisplay() +{ + char txt[16]; + float ww = (float)getParentWindow().getWidth(); + float wh = (float)getParentWindow().getHeight(); + float w = (float)fImage.getWidth(); + float h = (float)fImage.getHeight(); + + NanoVG::beginFrame(ww, wh); + if (fValue > 1000.f) { + snprintf(txt, sizeof(txt), "%.0f", fValue); + } else { + snprintf(txt, sizeof(txt), "%.1f", fValue); + } + FontId fid = findFont(NANOVG_DEJAVU_SANS_TTF); + fontFaceId(fid); + fontSize(14.f); + fillColor(255, 255, 255, 255); + strokeColor(255, 255, 255, 255); + textAlign(ALIGN_CENTER | ALIGN_MIDDLE); + textLineHeight(14.f); + text(w/2., h/2., txt, NULL); + NanoVG::endFrame(); +} + +void ZamKnob::onDisplay() +{ + const float normValue = ((fUsingLog ? _invlogscale(fValue) : fValue) - fMinimum) / (fMaximum - fMinimum); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, fTextureId); + + if (! fIsReady) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + + static const float trans[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, trans); + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + uint imageDataOffset = 0; + + if (fRotationAngle == 0) + { + DISTRHO_SAFE_ASSERT_RETURN(fImgLayerCount > 0,); + DISTRHO_SAFE_ASSERT_RETURN(normValue >= 0.0f,); + + const uint& v1(fIsImgVertical ? fImgLayerWidth : fImgLayerHeight); + const uint& v2(fIsImgVertical ? fImgLayerHeight : fImgLayerWidth); + + const uint layerDataSize = v1 * v2 * ((fImage.getFormat() == GL_BGRA || fImage.getFormat() == GL_RGBA) ? 4 : 3); + imageDataOffset = layerDataSize * uint(normValue * float(fImgLayerCount-1)); + } + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, + static_cast(getWidth()), static_cast(getHeight()), 0, + fImage.getFormat(), fImage.getType(), fImage.getRawData() + imageDataOffset); + + fIsReady = true; + } + + const int w = static_cast(getWidth()); + const int h = static_cast(getHeight()); + + if (fRotationAngle != 0) + { + glPushMatrix(); + + const int w2 = w/2; + const int h2 = h/2; + + glTranslatef(static_cast(w2), static_cast(h2), 0.0f); + glRotatef(normValue*static_cast(fRotationAngle), 0.0f, 0.0f, 1.0f); + + Rectangle(-w2, -h2, w, h).draw(); + + glPopMatrix(); + } + else + { + Rectangle(0, 0, w, h).draw(); + } + + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + + if (fLabel) + labelDisplay(); +} + +bool ZamKnob::onMouse(const MouseEvent& ev) +{ + if (ev.button != 1) + return false; + + if (ev.press) + { + if (! contains(ev.pos)) + return false; + + if ((ev.mod & kModifierShift) != 0 && fUsingDefault) + { + setValue(fValueDef, true); + fValueTmp = fValue; + return true; + } + + fDragging = true; + fLastX = ev.pos.getX(); + fLastY = ev.pos.getY(); + + if (fCallback != nullptr) + fCallback->imageKnobDragStarted(this); + + return true; + } + else if (fDragging) + { + if (fCallback != nullptr) + fCallback->imageKnobDragFinished(this); + + fDragging = false; + return true; + } + + return false; +} + +bool ZamKnob::onMotion(const MotionEvent& ev) +{ + if (! fDragging) + return false; + + bool doVal = false; + float d, value = 0.0f; + + if (fOrientation == ZamKnob::Horizontal) + { + if (const int movX = ev.pos.getX() - fLastX) + { + d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; + value = (fUsingLog ? _invlogscale(fValueTmp) : fValueTmp) + (float(fMaximum - fMinimum) / d * float(movX)); + doVal = true; + } + } + else if (fOrientation == ZamKnob::Vertical) + { + if (const int movY = fLastY - ev.pos.getY()) + { + d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; + value = (fUsingLog ? _invlogscale(fValueTmp) : fValueTmp) + (float(fMaximum - fMinimum) / d * float(movY)); + doVal = true; + } + } + + if (! doVal) + return false; + + if (fUsingLog) + value = _logscale(value); + + if (value < fMinimum) + { + fValueTmp = value = fMinimum; + } + else if (value > fMaximum) + { + fValueTmp = value = fMaximum; + } + else if (d_isNotZero(fStep)) + { + fValueTmp = value; + const float rest = std::fmod(value, fStep); + value = value - rest + (rest > fStep/2.0f ? fStep : 0.0f); + } + + setValue(value, true); + + fLastX = ev.pos.getX(); + fLastY = ev.pos.getY(); + + return true; +} + +bool ZamKnob::onScroll(const ScrollEvent& ev) +{ + if (! contains(ev.pos)) + return false; + + float value, d; + + if (d_isZero(fScrollStep)) { + d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; + value = (fUsingLog ? _invlogscale(fValueTmp) : fValueTmp) + (float(fMaximum - fMinimum) / d * 10.f * ev.delta.getY()); + } else { + d = (ev.mod & kModifierControl) ? fScrollStep * 0.1f : fScrollStep; + value = (fUsingLog ? _invlogscale(fValueTmp + d * ev.delta.getY()) + : fValueTmp + d * ev.delta.getY()); + } + + if (fUsingLog) + value = _logscale(value); + + if (value < fMinimum) + { + fValueTmp = value = fMinimum; + } + else if (value > fMaximum) + { + fValueTmp = value = fMaximum; + } + else if (d_isNotZero(fStep)) + { + fValueTmp = value; + const float rest = std::fmod(value, fStep); + value = value - rest + (rest > fStep/2.0f ? fStep : 0.0f); + } + + setValue(value, true); + return true; +} + +// ----------------------------------------------------------------------- + +float ZamKnob::_logscale(float value) const +{ + const float b = std::log(fMaximum/fMinimum)/(fMaximum-fMinimum); + const float a = fMaximum/std::exp(fMaximum*b); + if (value < fMinimum) value = fMinimum; + if (value > fMaximum) value = fMaximum; + return a * std::exp(b*value); +} + +float ZamKnob::_invlogscale(float value) const +{ + const float b = std::log(fMaximum/fMinimum)/(fMaximum-fMinimum); + const float a = fMaximum/std::exp(fMaximum*b); + if (value < fMinimum) value = fMinimum; + if (value > fMaximum) value = fMaximum; + return std::log(value/a)/b; +} + +END_NAMESPACE_DGL + +#endif -- cgit v1.2.3