xybrid/xybrid/ui/gadgets/knobgadget.cpp

233 lines
7.5 KiB
C++

#include "knobgadget.h"
using namespace Xybrid::UI;
#include "util/strings.h"
#include "ui/gadgets/layoutgadget.h"
#include "nodelib/basics.h"
#include "config/uiconfig.h"
using namespace Xybrid::Config;
#include <cmath>
#include <QDebug>
#include <QPainter>
#include <QRadialGradient>
#include <QGraphicsSceneMouseEvent>
template KnobGadget* KnobGadget::bind(double&);
template KnobGadget* KnobGadget::bind(float&);
template KnobGadget* KnobGadget::bind(int8_t&);
template KnobGadget* KnobGadget::bind(uint8_t&);
template KnobGadget* KnobGadget::bind(int16_t&);
template KnobGadget* KnobGadget::bind(uint16_t&);
template KnobGadget* KnobGadget::bind(int32_t&);
template KnobGadget* KnobGadget::bind(uint32_t&);
template KnobGadget* KnobGadget::bind(std::atomic<double>&);
template KnobGadget* KnobGadget::bind(std::atomic<float>&);
template KnobGadget* KnobGadget::bind(std::atomic<int8_t>&);
template KnobGadget* KnobGadget::bind(std::atomic<uint8_t>&);
template KnobGadget* KnobGadget::bind(std::atomic<int16_t>&);
template KnobGadget* KnobGadget::bind(std::atomic<uint16_t>&);
template KnobGadget* KnobGadget::bind(std::atomic<int32_t>&);
template KnobGadget* KnobGadget::bind(std::atomic<uint32_t>&);
double KnobGadget::get() {
double v = fGet();
if (v != lastVal) {
lastVal = v;
dirty = true;
}
return v;
}
KnobGadget::KnobGadget(QGraphicsItem* parent) : Gadget(parent) {
static QFont f("Arcon Rounded", 7);
label = new QGraphicsSimpleTextItem(this);
valLabel = new QGraphicsSimpleTextItem(this);
label->setBrush(QColor(255, 255, 255));
valLabel->setBrush(QColor(255, 255, 255));
label->setFont(f);
valLabel->setFont(f);
//label->setScale(0.8);
//valLabel->setScale(0.8);
}
QRectF KnobGadget::boundingRect() const {
return QRectF(0, 0, size, size);
}
QRectF KnobGadget::layoutBoundingRect() const {
constexpr qreal lh = 14;
return boundingRect().adjusted(0, -lh, 0, lh);
}
QPainterPath KnobGadget::shape() const {
QPainterPath p;
p.addEllipse(boundingRect());
return p;
}
void KnobGadget::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) {
auto val = get();
auto proportion = (val - min) / (max - min);
auto r = boundingRect();
auto ir = r - QMarginsF(2, 2, 2, 2);
if (dirty) {
dirty = false;
valLabel->setText(fText(val));
const qreal pad = 2;
auto ls = label->boundingRect().size() * label->scale();
label->setPos(r.center().x() - ls.width() / 2.0, r.height() + pad);
auto vls = valLabel->boundingRect().size() * valLabel->scale();
valLabel->setPos(r.center().x() - vls.width() / 2.0, -vls.height() - pad);
}
QColor outline = QColor(31, 31, 31);
if (highlighted) outline = QColor(127, 127, 255);
QColor needle = QColor(127, 127, 127);
// base circle
painter->setPen(Qt::NoPen);
painter->setBrush(outline);
painter->drawEllipse(r);
// range
painter->setBrush(QColor(63, 63, 63));
painter->drawPie(ir, 250*16, -320*16);
// fill
painter->setBrush(QColor(95, 95, 95));
painter->drawPie(ir, 250*16, static_cast<int>(-320.0*16*proportion));
// needle
QRadialGradient gr = QRadialGradient(ir.center(), ir.width() / 2.0, ir.center());
gr.setColorAt(0.0, QColor(0, 0, 0, 0));
gr.setColorAt(1.0, needle);
painter->setPen(QPen(gr, 1.25));
painter->setBrush(Qt::NoBrush);
painter->drawPie(ir, 250*16 + static_cast<int>(-320.0*16*proportion), 0);
}
namespace { // interaction tracking vars
int acc = 0;
int wAcc = 0;
double trackVal;
inline double stepRound(double v, double s) { return std::round(v / s) * s; }
int accumulate(int& acc, int step) {
int sgn = acc < 0 ? -1 : 1;
int rm = std::abs(acc) % step;
int diff = (std::abs(acc)-rm)*sgn / step;
acc = rm*sgn;
return diff;
}
}
void KnobGadget::hoverEnterEvent(QGraphicsSceneHoverEvent*) {
wAcc = 0; //reset wheel accumulator on hovering over a new knob
}
void KnobGadget::mousePressEvent(QGraphicsSceneMouseEvent* e) {
if (e->button() == Qt::LeftButton) {
highlighted = true;
trackVal = get();
auto mod = e->modifiers();
if (mod.testFlag(Qt::AltModifier)) ; // no rounding until alt released
else if (mod.testFlag(Qt::ShiftModifier) && subStep > 0.0)
trackVal = stepRound(trackVal, subStep);
else if (step > 0.0)
trackVal = stepRound(trackVal, step);
fSet(trackVal);
} else if (e->button() == Qt::RightButton) {
fSet(defaultVal);
e->accept();
}
update();
}
void KnobGadget::mouseReleaseEvent(QGraphicsSceneMouseEvent* e) {
if (e->button() == Qt::LeftButton) {
highlighted = false;
}
update();
}
void KnobGadget::mouseMoveEvent(QGraphicsSceneMouseEvent* e) {
if (highlighted) {
auto curPos = e->pos().toPoint();
auto lastPos = e->lastPos().toPoint();
if (UIConfig::verticalKnobs) acc -= curPos.y() - lastPos.y();
else acc += curPos.x() - lastPos.x();
auto d = accumulate(acc, stepPx);
auto mod = e->modifiers();
if (mod.testFlag(Qt::AltModifier)) trackVal = std::clamp(trackVal, min, max); // soft release; just keep track within range
else if (mod.testFlag(Qt::ShiftModifier) && subStep > 0) {
trackVal = stepRound(trackVal + subStep*d, subStep);
fSet(std::clamp(trackVal, min, max));
} else if (step > 0) {
trackVal = stepRound(trackVal + step*d, step);
fSet(std::clamp(trackVal, min, max));
}
}
update();
}
void KnobGadget::wheelEvent(QGraphicsSceneWheelEvent* e) {
e->accept(); // never pass through
wAcc += e->delta();
auto d = accumulate(wAcc, 120);
if (d == 0) return; // don't need to update anything if incomplete accumulation
if (UIConfig::invertScrollWheel) d *= -1;
auto mod = e->modifiers();
if (highlighted) { // wheel while dragging
/*if (mod.testFlag(Qt::ShiftModifier) && subStep > 0) {
trackVal = stepRound(trackVal + subStep*d, subStep);
fSet(std::clamp(trackVal, min, max));
} else if (step > 0) {
trackVal = stepRound(trackVal + step*d, step);
fSet(std::clamp(trackVal, min, max));
}*/
return;
}
if (mod.testFlag(Qt::ShiftModifier) && subStep > 0)
fSet(std::clamp(stepRound(get()+subStep*d, subStep), min, max));
else if (step > 0)
fSet(std::clamp(stepRound(get()+step*d, step), min, max));
update();
}
void KnobGadget::contextMenuEvent(QGraphicsSceneContextMenuEvent* e) {
e->accept();
}
QString KnobGadget::textPercent(double d) { return qs("%1%").arg(d*100); }
QString KnobGadget::textOffset(double d) { return (d > 0 ? qs("+%1") : qs("%1")).arg(d); }
QString KnobGadget::textFrequency(double d) { return qs("%1Hz").arg(d); }
QString KnobGadget::textGain(double d) { return (d > 0 ? qs("+%1dB") : qs("%1dB")).arg(d); }
QString KnobGadget::textBalance(double d) { return (d > 0 ? qs("+%1%") : qs("%1%")).arg(d*100); }
void KnobGadget::autoCreate(LayoutGadget* l, NodeLib::ADSR& adsr) {
KnobGadget* k [[maybe_unused]];
k = (new KnobGadget(l))->setRange(0.0, 5.0, 0.01)->bind(adsr.a)->setLabel(qs("Attack"));
k = (new KnobGadget(l))->setRange(0.0, 5.0, 0.01)->bind(adsr.d)->setLabel(qs("Decay"));
k = KnobGadget::autoPercent(l, adsr.s)->setLabel(qs("Sustain"));
k = (new KnobGadget(l))->setRange(0.0, 5.0, 0.01)->bind(adsr.r)->setLabel(qs("Release"));
}