#include "DistrhoUI.hpp" #include "tablet.h" #ifdef DEBUG #include #include #endif START_NAMESPACE_DISTRHO //UI element for mapping the currently pressed pen button combination to a parameter. class ButtonMappingWidget : public NanoSubWidget, public ButtonEventHandler { public: ButtonMappingWidget( Widget* parent, float initialSize, float initialX, float initialY, Parameters associatedParameter, ButtonEventHandler::Callback* const callback) : x(initialX), y(initialY), size(initialSize), param(associatedParameter), isClicked(false), isPenPressed(false), mask(0), NanoSubWidget(parent), ButtonEventHandler(this) { setSize(Size(static_cast(size), static_cast(size))); setAbsolutePos((int)x, (int)y); ButtonEventHandler::setCallback(callback); } void onNanoDisplay() override { beginPath(); strokeColor(200, 200, 200); fillColor(0.5f, 0.5f, 0.5f, 0.5f * (isClicked + isPenPressed)); roundedRect(0.f, 0.f, size, size, 0.5f * size); stroke(); fill(); closePath(); } bool onMouse(const MouseEvent& ev) override { isClicked = ev.press; return ButtonEventHandler::mouseEvent(ev); } void setMask(const ButtonMask m) { mask = m; } bool matchesMask(const ButtonMask m) { return isPenPressed = (m && mask == m); } bool isClicked; bool isPenPressed; private: float x = 0; float y = 0; float size = 0; const Parameters param; ButtonMask mask; }; class TabUI : public UI, public ButtonEventHandler::Callback { static const uint kInitialWidth = 800; static const uint kInitialHeight = 600; public: TabUI() : UI(kInitialWidth, kInitialHeight), tab(getWindow().getNativeWindowHandle()), AButtonWidget(this, 75.f, 700.f, 500.f, kParameterButtonA, this), BButtonWidget(this, 75.f, 700.f, 400.f, kParameterButtonB, this) { #ifdef DGL_NO_SHARED_RESOURCES createFontFromFile("sans", "/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf"); #else loadSharedResources(); #endif if (!tab.initialized) return; //setGeometryConstraints(400, static_cast(300 * tabletAspectRatio), true, false); } protected: void getTabletData() { if (!tab.initialized || !tab.GetPacket(pkt)) return; if (pkt == lastPkt) return; if (pkt.x != lastPkt.x) setParameterValue(ktpax, pkt.x); if (pkt.y != lastPkt.y) setParameterValue(ktpay, pkt.y); if (pkt.z != lastPkt.z) setParameterValue(ktpaz, pkt.z); if (pkt.p != lastPkt.p) setParameterValue(ktpap, pkt.p); if (pkt.buttons != lastPkt.buttons) setButtonsValue(pkt.buttons); lastPkt = pkt; } void setButtonsValue(unsigned long buttonMask) { if (AButtonWidget.matchesMask(buttonMask)) setParameterValue(kParameterButtonA, 1.f); else setParameterValue(kParameterButtonA, 0.f); if (BButtonWidget.matchesMask(buttonMask)) setParameterValue(kParameterButtonB, 1.f); else setParameterValue(kParameterButtonB, 0.f); } void buttonClicked(SubWidget* const widget, int ) override { if (widget == &AButtonWidget) AButtonWidget.setMask(pkt.buttons); if (widget == &BButtonWidget) BButtonWidget.setMask(pkt.buttons); } void parameterChanged(uint32_t index, float value) override { if (index != kParameterTime && index < kParameterCount) { switch (index) { case(ktpax): pkt.x = value; case(ktpay): pkt.y = value; case(ktpaz): pkt.z = value; case(ktpap): pkt.p = value; } return; } getTabletData(); repaint(); } void uiIdle() override { getTabletData(); repaint(); } bool onMouse(const MouseEvent& ev) override { getTabletData(); repaint(); return false; //Allow event to propagate. } bool onScroll(const ScrollEvent& ev) override { double add; const uint x = getWidth(); const uint y = getHeight(); add = (ev.delta.getY() > 0) ? 1 : -1; float tabletAspectRatio; if (tab.initialized) tabletAspectRatio = tab.ext.y / tab.ext.x; else tabletAspectRatio = 1.f; setSize(static_cast(x + add + 0.5), static_cast(tabletAspectRatio * (x + add + 0.5))); return true; } void onNanoDisplay() override { fontSize(15.0f); textLineHeight(1.f); //Report tablet errors. if (!tab.initialized) { const std::string err = std::vformat("Tablet not supported:\n{}", std::make_format_args(tab.errormsg)); beginPath(); fillColor(200, 200, 200); textBox(0.f, 15.f, 250.f, err.c_str(), nullptr); closePath(); return; } //Numerical feedback. beginPath(); fillColor(200, 200, 200); textBox(0.f, 15.f, 250.f, std::format("x: {:.3f}\ny: {:.3f}\nz: {:.3f}\np: {:.3f}\nb: {}", pkt.x, pkt.y, pkt.z, pkt.p, pkt.buttons).c_str(), nullptr); closePath(); //Pen position and pressure. drawCircle(pkt.x, pkt.y, pkt.z, pkt.p); } void drawCircle(float x, float y, float z, float p) { static constexpr float circleRadius = 25.f; x *= getWidth(); y = (1.f - y) * getHeight(); z = 1.f - z; beginPath(); strokeColor(1.f, 1.f, 1.f, 0.5f); moveTo(x - z * circleRadius, y); lineTo(x + z * circleRadius, y); stroke(); closePath(); beginPath(); strokeColor(1.f, 1.f, 1.f, 0.5f); moveTo(x, y - z * circleRadius); lineTo(x, y + z * circleRadius); stroke(); closePath(); beginPath(); fillColor(1.f, 1.f, 1.f, p); strokeColor(255, 255, 255, 255); circle(x, y, circleRadius); fill(); stroke(); closePath(); beginPath(); strokeColor(1.f, 1.f, 1.f, z); circle(x, y, z * circleRadius); stroke(); closePath(); } // ------------------------------------------------------------------------------------------------------- private: // Tablet context handler Tablet tab; Packet pkt = { 0 }; Packet lastPkt = { 0 }; // Button mapping widgets ButtonMappingWidget AButtonWidget; ButtonMappingWidget BButtonWidget; /** Set our UI class as non-copyable and add a leak detector just in case. */ DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TabUI) }; UI* createUI() { return new TabUI(); } END_NAMESPACE_DISTRHO