yaw-audio/src/yaw-tab/ui.cpp

266 lines
5.9 KiB
C++

#include "DistrhoUI.hpp"
#include "tablet.h"
#ifdef DEBUG
#include <format>
#include <optional>
#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<uint>(static_cast<uint>(size), static_cast<uint>(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<uint>(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<uint>(x + add + 0.5), static_cast<uint>(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