diff --git a/pdvg.hpp b/pdvg.hpp index cba7805..b93f158 100644 --- a/pdvg.hpp +++ b/pdvg.hpp @@ -5,6 +5,7 @@ #include "src/Bang.hpp" #include "src/Canvas.hpp" +#include "src/Jutils.hpp" #include "src/Common.hpp" #include "src/Comment.hpp" #include "src/DragNum.hpp" @@ -19,3 +20,4 @@ #include "src/Slider.hpp" #include "src/Subpatch.hpp" #include "src/Knob.hpp" +#include "src/Popmenu.hpp" diff --git a/src/Common.hpp b/src/Common.hpp index e192c26..888650c 100644 --- a/src/Common.hpp +++ b/src/Common.hpp @@ -9,6 +9,7 @@ #include #include "nanovg.h" #include "Widget.hpp" +#include "Jutils.hpp" START_NAMESPACE_DISTRHO @@ -64,14 +65,12 @@ inline DGL::Rectangle reduceRectangle(DGL::Rectangle r, float amou { // (x + delta, y + delta, w - delta * 2, h - delta * 2) - DGL::Rectangle nR( + return DGL::Rectangle( r.getX() + amount, r.getY() + amount, r.getWidth() - amount * 2, r.getHeight() - amount * 2 ); - - return nR; } inline DGL::Rectangle subtractBorder(DGL::Rectangle r, Border border) @@ -84,52 +83,44 @@ inline DGL::Rectangle subtractBorder(DGL::Rectangle r, Border bord ); } -inline float valToPropOfLen(float const value, float const length) +static DGL::Rectangle removeFromRight(DGL::Rectangle r, float amount) { - return value / length; -} - - -/** Some utility functions taken from JUCE ISC code */ + float amountRemove = jmin(amount, r.getWidth()); -/** Returns the smaller of two values. */ -template -constexpr Type jmin (Type a, Type b) { return b < a ? b : a; } - -/** Returns the larger of two values. */ -template -constexpr Type jmax (Type a, Type b) { return a < b ? b : a; } - -/** Returns the larger of three values. */ -template -constexpr Type jmax (Type a, Type b, Type c) { return a < b ? (b < c ? c : b) : (a < c ? c : a); } + return DGL::Rectangle( + r.getX() + r.getWidth() - amountRemove, + r.getY(), + amountRemove, + r.getHeight() + ); +} -/** Remaps a normalised value (between 0 and 1) to a target range. - This effectively returns (targetRangeMin + value0To1 * (targetRangeMax - targetRangeMin)). -*/ -template -constexpr Type jmap (Type value0To1, Type targetRangeMin, Type targetRangeMax) +static DGL::Rectangle resizeCentered(DGL::Rectangle r, float width, float height) { - return targetRangeMin + value0To1 * (targetRangeMax - targetRangeMin); + return DGL::Rectangle( + r.getX() + (r.getWidth() - width) / 2.0f, + r.getY() + (r.getHeight() - height) / 2.0f, + width, + height + ); } -/** Remaps a value from a source range to a target range. */ -template -Type jmap (Type sourceValue, Type sourceRangeMin, Type sourceRangeMax, Type targetRangeMin, Type targetRangeMax) +static DGL::Rectangle translateRectangle(DGL::Rectangle r, float x, float y) { - return targetRangeMin + ((targetRangeMax - targetRangeMin) * (sourceValue - sourceRangeMin)) / (sourceRangeMax - sourceRangeMin); + return DGL::Rectangle( + r.getX() + x, + r.getY() + y, + r.getWidth(), + r.getHeight() + ); } -/** Converts an angle in degrees to radians. */ -template -constexpr FloatType degreesToRadians (FloatType degrees) noexcept { return degrees * (NVG_PI / FloatType (180)); } - -/** Converts an angle in radians to degrees. */ -template -constexpr FloatType radiansToDegrees (FloatType radians) noexcept { return radians * (FloatType (180) / NVG_PI); } - +static float valToPropOfLen(float const value, float const length) +{ + return value / length; +} -inline float getColorBrightness(NVGcolor c) +static float getColorBrightness(NVGcolor c) { float brightness = 0.0f; diff --git a/src/ExtraEventHandlers.cpp b/src/ExtraEventHandlers.cpp index 459f9dc..7249c68 100644 --- a/src/ExtraEventHandlers.cpp +++ b/src/ExtraEventHandlers.cpp @@ -36,7 +36,6 @@ struct PDToggleEventHandler::PrivateData widget(w), callback(other->callback), isDown(other->isDown) - { } @@ -682,7 +681,6 @@ struct PDRadioEventHandler::PrivateData bool mouseEvent(const Widget::MouseEvent &ev) { - if (ev.button != 1) return false; @@ -1655,5 +1653,129 @@ bool PDKnobEventHandler::scrollEvent(const Widget::ScrollEvent &ev) return pData->scrollEvent(ev); } // end knob +// -------------------------------------------------------------------------------------------------------------------- + +// begin popmenu +struct PDPopmenuEventHandler::PrivateData +{ + PDPopmenuEventHandler *const self; + SubWidget *const widget; + PDPopmenuEventHandler::Callback *callback; + + uint length; + uint value; + + PrivateData(PDPopmenuEventHandler *const s, SubWidget *const w) + : self(s), + widget(w), + callback(nullptr), + length(0), + value(0) + { + } + + PrivateData(PDPopmenuEventHandler *const s, SubWidget *const w, PrivateData *const other) + : self(s), + widget(w), + callback(other->callback), + length(other->length), + value(other->value) + { + } + + void assignFrom(PrivateData *const other) + { + callback = other->callback; + length = other->length; + value = other->value; + } + + bool mouseEvent(const Widget::MouseEvent &ev) + { + if (ev.button != 1) + return false; + + PDWidget* pdWidget = dynamic_cast(widget); + if (ev.press && pdWidget->contains(ev.pos)) + { + return true; + } + return false; + } + + bool motionEvent(const Widget::MotionEvent &ev) + { + return false; + } + + bool setValue(const uint value2, const bool sendCallback) + { + if (value != value2) + { + value = value2; + if (sendCallback && callback != nullptr) + { + try + { + callback->popmenuClicked(widget, value); + } + DISTRHO_SAFE_EXCEPTION("PDPopmenuEventHandler::setValue"); + } + return true; + } + return false; + } +}; + +// -------------------------------------------------------------------------------------------------------------------- + +PDPopmenuEventHandler::PDPopmenuEventHandler(SubWidget *const self) + : pData(new PrivateData(this, self)) {} + +PDPopmenuEventHandler::PDPopmenuEventHandler(SubWidget *const self, const PDPopmenuEventHandler &other) + : pData(new PrivateData(this, self, other.pData)) {} + +PDPopmenuEventHandler &PDPopmenuEventHandler::operator=(const PDPopmenuEventHandler &other) +{ + pData->assignFrom(other.pData); + return *this; +} + +PDPopmenuEventHandler::~PDPopmenuEventHandler() +{ + delete pData; +} + +uint PDPopmenuEventHandler::getValue() const noexcept +{ + return pData->value; +} + +bool PDPopmenuEventHandler::setValue(const int value, const bool sendCallback) noexcept +{ + return pData->setValue(static_cast(value), sendCallback); +} + +void PDPopmenuEventHandler::setLength(int length) noexcept +{ + pData->length = length; +} + +void PDPopmenuEventHandler::setCallback(Callback *const callback) noexcept +{ + pData->callback = callback; +} + +bool PDPopmenuEventHandler::mouseEvent(const Widget::MouseEvent &ev) +{ + return pData->mouseEvent(ev); +} + +bool PDPopmenuEventHandler::motionEvent(const Widget::MotionEvent &ev) +{ + return pData->motionEvent(ev); +} + +// end popmenu END_NAMESPACE_DGL diff --git a/src/ExtraEventHandlers.hpp b/src/ExtraEventHandlers.hpp index d6ecf2c..35e1b3d 100644 --- a/src/ExtraEventHandlers.hpp +++ b/src/ExtraEventHandlers.hpp @@ -315,6 +315,39 @@ class PDKnobEventHandler DISTRHO_LEAK_DETECTOR(PDKnobEventHandler) }; +class PDPopmenuEventHandler +{ +public: + class Callback + { + public: + virtual ~Callback() {} + virtual void popmenuClicked(SubWidget *widget, uint index) = 0; + }; + + explicit PDPopmenuEventHandler(SubWidget *self); + explicit PDPopmenuEventHandler(SubWidget *self, const PDPopmenuEventHandler &other); + PDPopmenuEventHandler &operator=(const PDPopmenuEventHandler &other); + ~PDPopmenuEventHandler(); + + uint getValue() const noexcept; + + virtual bool setValue(int value, bool sendCallback = false) noexcept; + + void setLength(int length) noexcept; + void setCallback(Callback *callback) noexcept; + + bool mouseEvent(const Widget::MouseEvent &ev); + bool motionEvent(const Widget::MotionEvent &ev); + +protected: +private: + struct PrivateData; + PrivateData *const pData; + + DISTRHO_LEAK_DETECTOR(PDPopmenuEventHandler) +}; + // -------------------------------------------------------------------------------------------------------------------- END_NAMESPACE_DGL diff --git a/src/Jutils.hpp b/src/Jutils.hpp new file mode 100644 index 0000000..6f380dd --- /dev/null +++ b/src/Jutils.hpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022 - Raw Material Software Limited + * SPDX-License-Identifier: ISC +*/ + +#pragma once + +#include "nanovg.h" + +START_NAMESPACE_DISTRHO + +/** Some utility functions taken from JUCE ISC code */ + +/** Returns the smaller of two values. */ +template +constexpr Type jmin (Type a, Type b) { return b < a ? b : a; } + +/** Returns the larger of two values. */ +template +constexpr Type jmax (Type a, Type b) { return a < b ? b : a; } + +/** Returns the larger of three values. */ +template +constexpr Type jmax (Type a, Type b, Type c) { return a < b ? (b < c ? c : b) : (a < c ? c : a); } + +/** Remaps a normalised value (between 0 and 1) to a target range. + This effectively returns (targetRangeMin + value0To1 * (targetRangeMax - targetRangeMin)). +*/ +template +constexpr Type jmap (Type value0To1, Type targetRangeMin, Type targetRangeMax) +{ + return targetRangeMin + value0To1 * (targetRangeMax - targetRangeMin); +} + +/** Remaps a value from a source range to a target range. */ +template +Type jmap (Type sourceValue, Type sourceRangeMin, Type sourceRangeMax, Type targetRangeMin, Type targetRangeMax) +{ + return targetRangeMin + ((targetRangeMax - targetRangeMin) * (sourceValue - sourceRangeMin)) / (sourceRangeMax - sourceRangeMin); +} + +/** Converts an angle in degrees to radians. */ +template +constexpr FloatType degreesToRadians (FloatType degrees) noexcept { return degrees * (NVG_PI / FloatType (180)); } + +/** Converts an angle in radians to degrees. */ +template +constexpr FloatType radiansToDegrees (FloatType radians) noexcept { return radians * (FloatType (180) / NVG_PI); } + +END_NAMESPACE_DISTRHO diff --git a/src/Popmenu.cpp b/src/Popmenu.cpp new file mode 100644 index 0000000..ac03bc0 --- /dev/null +++ b/src/Popmenu.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2026 Wasted Audio + * SPDX-License-Identifier: ISC +*/ + +#include "nanovg.h" + +#include "Common.hpp" +#include "Popmenu.hpp" +#include "Fonts/InterRegular.hpp" + +START_NAMESPACE_DISTRHO + +PDPopmenu::PDPopmenu(NanoSubWidget* parent, PDPopmenuEventHandler::Callback* cb) + : PDWidget(parent), + PDPopmenuEventHandler(this) +{ + PDPopmenuEventHandler::setCallback(cb); + + using namespace InterRegular; + NanoVG:FontId interId = createFontFromMemory("inter", (const uchar *)InterRegularData, InterRegularDataSize, 0); + fFontId = interId; +} + +void PDPopmenu::renderText(NVGcontext* nvg, DGL::Rectangle bounds) +{ + beginPath(); + + auto fAlign = NVG_ALIGN_TOP; + auto value = (int) getValue(); + auto fText = options[value].c_str(); + + fontFaceId(fFontId); + fontSize(fFontSize * 1.5f); + fillColor(Colors::cnvTextColor); + textAlign(fAlign); + text(bounds.getX(), bounds.getY(), fText, NULL); + closePath(); +} + +void PDPopmenu::onNanoDisplay() +{ + const float scaleFactor = getTopLevelWidget()->getScaleFactor(); + const DGL::Rectangle b(0.0f, 0.0f, getWidth(), getHeight()); + + NVGcontext* nvg = getContext(); + + drawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), bgColor, Colors::outColor, Corners::objectCornerRadius); + + auto textBounds = translateRectangle(reduceRectangle(b, 2 * scaleFactor), 2 * scaleFactor, 0); + renderText(nvg, textBounds); + + auto const triangleBounds = resizeCentered(removeFromRight(b, 20), 20, std::min((int)b.getHeight(), 12)); + + auto const triangleBoundsCenterX = triangleBounds.getX() + triangleBounds.getWidth() / 2.0f; + auto const triangleBoundsBottom = triangleBounds.getY() + triangleBounds.getHeight(); + + nvgStrokeColor(nvg, fgColor); + nvgBeginPath(nvg); + nvgMoveTo(nvg, triangleBoundsCenterX - 3 * scaleFactor, triangleBounds.getY() + 3 * scaleFactor); + nvgLineTo(nvg, triangleBoundsCenterX, triangleBounds.getY()); + nvgLineTo(nvg, triangleBoundsCenterX + 3 * scaleFactor, triangleBounds.getY() + 3 * scaleFactor); + nvgStroke(nvg); + + nvgBeginPath(nvg); + nvgMoveTo(nvg, triangleBoundsCenterX - 3 * scaleFactor, triangleBoundsBottom - 3 * scaleFactor); + nvgLineTo(nvg, triangleBoundsCenterX, triangleBoundsBottom); + nvgLineTo(nvg, triangleBoundsCenterX + 3 * scaleFactor, triangleBoundsBottom - 3 * scaleFactor); + nvgStroke(nvg); +} + +bool PDPopmenu::onMouse(const MouseEvent &ev) +{ + return PDPopmenuEventHandler::mouseEvent(ev); +} + +bool PDPopmenu::onMotion(const MotionEvent &ev) +{ + return PDPopmenuEventHandler::motionEvent(ev); +} + +void PDPopmenu::setColors(NVGcolor bgColor, NVGcolor fgColor) +{ + this->bgColor = bgColor; + this->fgColor = fgColor; +} + +void PDPopmenu::setNoSelectLabel(std::string label) +{ + this->noSelectLabel = label; +} + +void PDPopmenu::setOptions(std::vector options) +{ + this->options = options; +} + +void PDPopmenu::setFontSize(int fontSize) +{ + this->fFontSize = fontSize; +} + +END_NAMESPACE_DISTRHO diff --git a/src/Popmenu.hpp b/src/Popmenu.hpp new file mode 100644 index 0000000..a030b70 --- /dev/null +++ b/src/Popmenu.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2026 Wasted Audio + * SPDX-License-Identifier: ISC +*/ + +#pragma once + +#include "NanoVG.hpp" +#include "nanovg.h" + +#include "ExtraEventHandlers.hpp" +#include "Label.hpp" +#include "PDWidget.hpp" + +START_NAMESPACE_DISTRHO + +class PDPopmenu : public PDWidget, + public PDPopmenuEventHandler +{ +public: + explicit PDPopmenu(NanoSubWidget* parent, PDPopmenuEventHandler::Callback* cb); + + void setColors(NVGcolor bgColor, NVGcolor fgColor); + void setNoSelectLabel(std::string label); + void setOptions(std::vector options); + void setFontSize(int size); + +protected: + bool onMouse(const MouseEvent &ev) override; + bool onMotion(const MotionEvent &ev) override; + void renderText(NVGcontext* nvg, DGL::Rectangle bounds); + void onNanoDisplay() override; + +private: + NVGcolor fgColor; + NVGcolor bgColor; + int fFontSize; + std::string noSelectLabel; + std::vector options; + + NanoVG::FontId fFontId; + + DISTRHO_LEAK_DETECTOR(PDPopmenu) +}; + +END_NAMESPACE_DISTRHO