232 lines
7.4 KiB
C++
232 lines
7.4 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
|
|
|
|
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"));
|
|
}
|