- Make ParamWidget hold module/paramId instead of paramQuantity. - Add configInput/configOutput. - Add engine::PortInfo. - Avoid calling particular events when cursor is locked. - Add PortTooltip.tags/v2.0.0
@@ -51,11 +51,11 @@ struct ModuleWidget : widget::OpaqueWidget { | |||
/** Convenience functions for adding special widgets (calls addChild()) */ | |||
void addParam(ParamWidget* param); | |||
void addOutput(PortWidget* output); | |||
void addInput(PortWidget* input); | |||
void addOutput(PortWidget* output); | |||
ParamWidget* getParam(int paramId); | |||
PortWidget* getOutput(int outputId); | |||
PortWidget* getInput(int inputId); | |||
PortWidget* getOutput(int outputId); | |||
/** Serializes/unserializes the module state */ | |||
json_t* toJson(); | |||
@@ -12,12 +12,15 @@ namespace app { | |||
/** Manages an engine::Param on a ModuleWidget. */ | |||
struct ParamWidget : widget::OpaqueWidget { | |||
engine::ParamQuantity* paramQuantity = NULL; | |||
engine::Module* module = NULL; | |||
int paramId = 0; | |||
ui::Tooltip* tooltip = NULL; | |||
/** For triggering the Change event. `*/ | |||
float lastValue = NAN; | |||
virtual void init() {} | |||
engine::ParamQuantity* getParamQuantity(); | |||
void step() override; | |||
void draw(const DrawArgs& args) override; | |||
@@ -1,8 +1,10 @@ | |||
#pragma once | |||
#include <app/common.hpp> | |||
#include <widget/OpaqueWidget.hpp> | |||
#include <ui/Tooltip.hpp> | |||
#include <app/MultiLightWidget.hpp> | |||
#include <engine/Module.hpp> | |||
#include <engine/PortInfo.hpp> | |||
namespace rack { | |||
@@ -12,18 +14,20 @@ namespace app { | |||
/** Manages an engine::Port on a ModuleWidget. */ | |||
struct PortWidget : widget::OpaqueWidget { | |||
engine::Module* module = NULL; | |||
int portId; | |||
int portId = 0; | |||
engine::Port::Type type = engine::Port::INPUT; | |||
ui::Tooltip* tooltip = NULL; | |||
bool hovered = false; | |||
enum Type { | |||
OUTPUT, | |||
INPUT | |||
}; | |||
Type type; | |||
MultiLightWidget* plugLight; | |||
PortWidget(); | |||
~PortWidget(); | |||
engine::Port* getPort(); | |||
engine::PortInfo* getPortInfo(); | |||
void createTooltip(); | |||
void destroyTooltip(); | |||
void step() override; | |||
void draw(const DrawArgs& args) override; | |||
@@ -577,8 +577,7 @@ struct LightSlider : TBase { | |||
} | |||
void setFirstLightId(int firstLightId) { | |||
if (this->paramQuantity) | |||
light->module = this->paramQuantity->module; | |||
light->module = this->module; | |||
light->firstLightId = firstLightId; | |||
} | |||
@@ -730,8 +729,7 @@ struct LEDLightBezel : LEDBezel { | |||
} | |||
void setFirstLightId(int firstLightId) { | |||
if (paramQuantity) | |||
light->module = paramQuantity->module; | |||
light->module = this->module; | |||
light->firstLightId = firstLightId; | |||
} | |||
}; | |||
@@ -6,6 +6,7 @@ | |||
#include <engine/Port.hpp> | |||
#include <engine/Light.hpp> | |||
#include <engine/ParamQuantity.hpp> | |||
#include <engine/PortInfo.hpp> | |||
#include <vector> | |||
#include <jansson.h> | |||
@@ -36,6 +37,8 @@ struct Module { | |||
std::vector<Output> outputs; | |||
std::vector<Light> lights; | |||
std::vector<ParamQuantity*> paramQuantities; | |||
std::vector<PortInfo*> inputInfos; | |||
std::vector<PortInfo*> outputInfos; | |||
/** Represents a message-passing channel for an adjacent module. */ | |||
struct Expander { | |||
@@ -106,10 +109,10 @@ struct Module { | |||
q->minValue = minValue; | |||
q->maxValue = maxValue; | |||
q->defaultValue = defaultValue; | |||
if (!label.empty()) | |||
q->label = label; | |||
if (label == "") | |||
q->label = string::f("Parameter %d", paramId + 1); | |||
else | |||
q->label = string::f("#%d", paramId + 1); | |||
q->label = label; | |||
q->unit = unit; | |||
q->displayBase = displayBase; | |||
q->displayMultiplier = displayMultiplier; | |||
@@ -117,6 +120,32 @@ struct Module { | |||
paramQuantities[paramId] = q; | |||
} | |||
void configInput(int portId, std::string label = "") { | |||
assert(portId < (int) inputs.size() && portId < (int) inputInfos.size()); | |||
if (inputInfos[portId]) | |||
delete inputInfos[portId]; | |||
PortInfo* p = new PortInfo; | |||
if (label == "") | |||
p->label = string::f("Input %d", portId + 1); | |||
else | |||
p->label = label; | |||
inputInfos[portId] = p; | |||
} | |||
void configOutput(int portId, std::string label = "") { | |||
assert(portId < (int) outputs.size() && portId < (int) outputInfos.size()); | |||
if (outputInfos[portId]) | |||
delete outputInfos[portId]; | |||
PortInfo* p = new PortInfo; | |||
if (label == "") | |||
p->label = string::f("Output %d", portId + 1); | |||
else | |||
p->label = label; | |||
outputInfos[portId] = p; | |||
} | |||
struct ProcessArgs { | |||
float sampleRate; | |||
float sampleTime; | |||
@@ -0,0 +1,21 @@ | |||
#pragma once | |||
#include <common.hpp> | |||
#include <engine/Port.hpp> | |||
namespace rack { | |||
namespace engine { | |||
struct PortInfo { | |||
/** The name of the port, using sentence capitalization. | |||
e.g. "Sine", "Pitch input", "Mode CV" | |||
*/ | |||
std::string label; | |||
/** An optional one-sentence description of the parameter. */ | |||
std::string description; | |||
}; | |||
} // namespace engine | |||
} // namespace rack |
@@ -59,9 +59,8 @@ template <class TParamWidget> | |||
TParamWidget* createParam(math::Vec pos, engine::Module* module, int paramId) { | |||
TParamWidget* o = new TParamWidget; | |||
o->box.pos = pos; | |||
if (module) { | |||
o->paramQuantity = module->paramQuantities[paramId]; | |||
} | |||
o->module = module; | |||
o->paramId = paramId; | |||
o->init(); | |||
return o; | |||
} | |||
@@ -78,7 +77,7 @@ TPortWidget* createInput(math::Vec pos, engine::Module* module, int inputId) { | |||
TPortWidget* o = new TPortWidget; | |||
o->box.pos = pos; | |||
o->module = module; | |||
o->type = app::PortWidget::INPUT; | |||
o->type = engine::Port::INPUT; | |||
o->portId = inputId; | |||
return o; | |||
} | |||
@@ -95,7 +94,7 @@ TPortWidget* createOutput(math::Vec pos, engine::Module* module, int outputId) { | |||
TPortWidget* o = new TPortWidget; | |||
o->box.pos = pos; | |||
o->module = module; | |||
o->type = app::PortWidget::OUTPUT; | |||
o->type = engine::Port::OUTPUT; | |||
o->portId = outputId; | |||
return o; | |||
} | |||
@@ -83,6 +83,7 @@ struct Window { | |||
void close(); | |||
void cursorLock(); | |||
void cursorUnlock(); | |||
bool isCursorLocked(); | |||
/** Gets the current keyboard mod state | |||
Don't call this from a Key event. Simply use `e.mods` instead. | |||
*/ | |||
@@ -14,9 +14,10 @@ static const float KNOB_SENSITIVITY = 0.0015f; | |||
void Knob::init() { | |||
ParamWidget::init(); | |||
if (paramQuantity) { | |||
engine::ParamQuantity* pq = getParamQuantity(); | |||
if (pq) { | |||
if (snap) | |||
paramQuantity->snapEnabled = true; | |||
pq->snapEnabled = true; | |||
} | |||
} | |||
@@ -40,8 +41,9 @@ void Knob::onDragStart(const event::DragStart& e) { | |||
if (e.button != GLFW_MOUSE_BUTTON_LEFT) | |||
return; | |||
if (paramQuantity) { | |||
oldValue = paramQuantity->getSmoothValue(); | |||
engine::ParamQuantity* pq = getParamQuantity(); | |||
if (pq) { | |||
oldValue = pq->getSmoothValue(); | |||
snapDelta = 0.f; | |||
} | |||
@@ -54,14 +56,15 @@ void Knob::onDragEnd(const event::DragEnd& e) { | |||
APP->window->cursorUnlock(); | |||
if (paramQuantity) { | |||
float newValue = paramQuantity->getSmoothValue(); | |||
engine::ParamQuantity* pq = getParamQuantity(); | |||
if (pq) { | |||
float newValue = pq->getSmoothValue(); | |||
if (oldValue != newValue) { | |||
// Push ParamChange history action | |||
history::ParamChange* h = new history::ParamChange; | |||
h->name = "move knob"; | |||
h->moduleId = paramQuantity->module->id; | |||
h->paramId = paramQuantity->paramId; | |||
h->moduleId = module->id; | |||
h->paramId = paramId; | |||
h->oldValue = oldValue; | |||
h->newValue = newValue; | |||
APP->history->push(h); | |||
@@ -73,10 +76,11 @@ void Knob::onDragMove(const event::DragMove& e) { | |||
if (e.button != GLFW_MOUSE_BUTTON_LEFT) | |||
return; | |||
if (paramQuantity) { | |||
engine::ParamQuantity* pq = getParamQuantity(); | |||
if (pq) { | |||
float range; | |||
if (paramQuantity->isBounded()) { | |||
range = paramQuantity->getRange(); | |||
if (pq->isBounded()) { | |||
range = pq->getRange(); | |||
} | |||
else { | |||
// Continuous encoders scale as if their limits are +/-1 | |||
@@ -97,7 +101,7 @@ void Knob::onDragMove(const event::DragMove& e) { | |||
delta /= 256.f; | |||
} | |||
if (paramQuantity->snapEnabled) { | |||
if (pq->snapEnabled) { | |||
// Replace delta with an accumulated delta since the last integer knob. | |||
snapDelta += delta; | |||
delta = std::trunc(snapDelta); | |||
@@ -106,10 +110,10 @@ void Knob::onDragMove(const event::DragMove& e) { | |||
// Set value | |||
if (smooth) { | |||
paramQuantity->setSmoothValue(paramQuantity->getSmoothValue() + delta); | |||
pq->setSmoothValue(pq->getSmoothValue() + delta); | |||
} | |||
else { | |||
paramQuantity->setValue(paramQuantity->getValue() + delta); | |||
pq->setValue(pq->getValue() + delta); | |||
} | |||
} | |||
@@ -449,21 +449,9 @@ void ModuleWidget::addParam(ParamWidget* param) { | |||
addChild(param); | |||
} | |||
void ModuleWidget::addOutput(PortWidget* output) { | |||
// Check that the port is an output | |||
assert(output->type == PortWidget::OUTPUT); | |||
// Check that the port doesn't have a duplicate ID | |||
for (PortWidget* output2 : outputs) { | |||
assert(output->portId != output2->portId); | |||
} | |||
// Add port | |||
outputs.push_back(output); | |||
addChild(output); | |||
} | |||
void ModuleWidget::addInput(PortWidget* input) { | |||
// Check that the port is an input | |||
assert(input->type == PortWidget::INPUT); | |||
assert(input->type == engine::Port::INPUT); | |||
// Check that the port doesn't have a duplicate ID | |||
for (PortWidget* input2 : inputs) { | |||
assert(input->portId != input2->portId); | |||
@@ -473,25 +461,37 @@ void ModuleWidget::addInput(PortWidget* input) { | |||
addChild(input); | |||
} | |||
void ModuleWidget::addOutput(PortWidget* output) { | |||
// Check that the port is an output | |||
assert(output->type == engine::Port::OUTPUT); | |||
// Check that the port doesn't have a duplicate ID | |||
for (PortWidget* output2 : outputs) { | |||
assert(output->portId != output2->portId); | |||
} | |||
// Add port | |||
outputs.push_back(output); | |||
addChild(output); | |||
} | |||
ParamWidget* ModuleWidget::getParam(int paramId) { | |||
for (ParamWidget* param : params) { | |||
if (param->paramQuantity && param->paramQuantity->paramId == paramId) | |||
if (param->paramId == paramId) | |||
return param; | |||
} | |||
return NULL; | |||
} | |||
PortWidget* ModuleWidget::getOutput(int outputId) { | |||
for (PortWidget* port : outputs) { | |||
if (port->portId == outputId) | |||
PortWidget* ModuleWidget::getInput(int inputId) { | |||
for (PortWidget* port : inputs) { | |||
if (port->portId == inputId) | |||
return port; | |||
} | |||
return NULL; | |||
} | |||
PortWidget* ModuleWidget::getInput(int inputId) { | |||
for (PortWidget* port : inputs) { | |||
if (port->portId == inputId) | |||
PortWidget* ModuleWidget::getOutput(int outputId) { | |||
for (PortWidget* port : outputs) { | |||
if (port->portId == outputId) | |||
return port; | |||
} | |||
return NULL; | |||
@@ -690,25 +690,25 @@ void ModuleWidget::randomizeAction() { | |||
} | |||
static void disconnectActions(ModuleWidget* mw, history::ComplexAction* complexAction) { | |||
// Add CableRemove action for all cables attached to outputs | |||
for (PortWidget* output : mw->outputs) { | |||
for (CableWidget* cw : APP->scene->rack->getCablesOnPort(output)) { | |||
// Add CableRemove action for all cables attached to inputs | |||
for (PortWidget* input : mw->inputs) { | |||
for (CableWidget* cw : APP->scene->rack->getCablesOnPort(input)) { | |||
if (!cw->isComplete()) | |||
continue; | |||
// Avoid creating duplicate actions for self-patched cables | |||
if (cw->outputPort->module == mw->module) | |||
continue; | |||
// history::CableRemove | |||
history::CableRemove* h = new history::CableRemove; | |||
h->setCable(cw); | |||
complexAction->push(h); | |||
} | |||
} | |||
// Add CableRemove action for all cables attached to inputs | |||
for (PortWidget* input : mw->inputs) { | |||
for (CableWidget* cw : APP->scene->rack->getCablesOnPort(input)) { | |||
// Add CableRemove action for all cables attached to outputs | |||
for (PortWidget* output : mw->outputs) { | |||
for (CableWidget* cw : APP->scene->rack->getCablesOnPort(output)) { | |||
if (!cw->isComplete()) | |||
continue; | |||
// Avoid creating duplicate actions for self-patched cables | |||
if (cw->outputPort->module == mw->module) | |||
continue; | |||
// history::CableRemove | |||
history::CableRemove* h = new history::CableRemove; | |||
h->setCable(cw); | |||
@@ -24,23 +24,26 @@ struct ParamField : ui::TextField { | |||
void setParamWidget(ParamWidget* paramWidget) { | |||
this->paramWidget = paramWidget; | |||
if (paramWidget->paramQuantity) | |||
text = paramWidget->paramQuantity->getDisplayValueString(); | |||
engine::ParamQuantity* pq = paramWidget->getParamQuantity(); | |||
if (pq) | |||
text = pq->getDisplayValueString(); | |||
selectAll(); | |||
} | |||
void onSelectKey(const event::SelectKey& e) override { | |||
if (e.action == GLFW_PRESS && (e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER)) { | |||
float oldValue = paramWidget->paramQuantity->getValue(); | |||
if (paramWidget->paramQuantity) | |||
paramWidget->paramQuantity->setDisplayValueString(text); | |||
float newValue = paramWidget->paramQuantity->getValue(); | |||
engine::ParamQuantity* pq = paramWidget->getParamQuantity(); | |||
assert(pq); | |||
float oldValue = pq->getValue(); | |||
if (pq) | |||
pq->setDisplayValueString(text); | |||
float newValue = pq->getValue(); | |||
if (oldValue != newValue) { | |||
// Push ParamChange history action | |||
history::ParamChange* h = new history::ParamChange; | |||
h->moduleId = paramWidget->paramQuantity->module->id; | |||
h->paramId = paramWidget->paramQuantity->paramId; | |||
h->moduleId = paramWidget->module->id; | |||
h->paramId = paramWidget->paramId; | |||
h->oldValue = oldValue; | |||
h->newValue = newValue; | |||
APP->history->push(h); | |||
@@ -61,13 +64,15 @@ struct ParamTooltip : ui::Tooltip { | |||
ParamWidget* paramWidget; | |||
void step() override { | |||
if (paramWidget->paramQuantity) { | |||
engine::ParamQuantity* pq = paramWidget->getParamQuantity(); | |||
if (pq) { | |||
// Quantity string | |||
text = paramWidget->paramQuantity->getString(); | |||
// Param description | |||
std::string description = paramWidget->paramQuantity->description; | |||
if (!description.empty()) | |||
text += "\n" + description; | |||
text = pq->getString(); | |||
// Description | |||
if (pq->description != "") { | |||
text += "\n"; | |||
text += pq->description; | |||
} | |||
} | |||
Tooltip::step(); | |||
// Position at bottom-right of parameter | |||
@@ -82,7 +87,8 @@ struct ParamTooltip : ui::Tooltip { | |||
struct ParamLabel : ui::MenuLabel { | |||
ParamWidget* paramWidget; | |||
void step() override { | |||
text = paramWidget->paramQuantity->getString(); | |||
engine::ParamQuantity* pq = paramWidget->getParamQuantity(); | |||
text = pq->getString(); | |||
MenuLabel::step(); | |||
} | |||
}; | |||
@@ -103,7 +109,7 @@ struct ParamFineItem : ui::MenuItem { | |||
struct ParamUnmapItem : ui::MenuItem { | |||
ParamWidget* paramWidget; | |||
void onAction(const event::Action& e) override { | |||
engine::ParamHandle* paramHandle = APP->engine->getParamHandle(paramWidget->paramQuantity->module->id, paramWidget->paramQuantity->paramId); | |||
engine::ParamHandle* paramHandle = APP->engine->getParamHandle(paramWidget->module->id, paramWidget->paramId); | |||
if (paramHandle) { | |||
APP->engine->updateParamHandle(paramHandle, -1, 0); | |||
} | |||
@@ -111,10 +117,17 @@ struct ParamUnmapItem : ui::MenuItem { | |||
}; | |||
engine::ParamQuantity* ParamWidget::getParamQuantity() { | |||
if (!module) | |||
return NULL; | |||
return module->paramQuantities[paramId]; | |||
} | |||
void ParamWidget::step() { | |||
if (paramQuantity) { | |||
float value = paramQuantity->getSmoothValue(); | |||
// Trigger change event when paramQuantity value changes | |||
engine::ParamQuantity* pq = getParamQuantity(); | |||
if (pq) { | |||
float value = pq->getSmoothValue(); | |||
// Trigger change event when the ParamQuantity value changes | |||
if (value != lastValue) { | |||
event::Change eChange; | |||
onChange(eChange); | |||
@@ -129,7 +142,7 @@ void ParamWidget::draw(const DrawArgs& args) { | |||
Widget::draw(args); | |||
// Param map indicator | |||
engine::ParamHandle* paramHandle = paramQuantity ? APP->engine->getParamHandle(paramQuantity->module->id, paramQuantity->paramId) : NULL; | |||
engine::ParamHandle* paramHandle = module ? APP->engine->getParamHandle(module->id, paramId) : NULL; | |||
if (paramHandle) { | |||
NVGcolor color = paramHandle->color; | |||
nvgBeginPath(args.vg); | |||
@@ -149,7 +162,7 @@ void ParamWidget::onButton(const event::Button& e) { | |||
// Touch parameter | |||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == 0) { | |||
if (paramQuantity) { | |||
if (module) { | |||
APP->scene->rack->touchedParam = this; | |||
} | |||
e.consume(this); | |||
@@ -167,11 +180,11 @@ void ParamWidget::onDoubleClick(const event::DoubleClick& e) { | |||
} | |||
void ParamWidget::onEnter(const event::Enter& e) { | |||
if (settings::paramTooltip && !tooltip && paramQuantity) { | |||
ParamTooltip* paramTooltip = new ParamTooltip; | |||
paramTooltip->paramWidget = this; | |||
APP->scene->addChild(paramTooltip); | |||
tooltip = paramTooltip; | |||
if (settings::paramTooltip && !this->tooltip && module) { | |||
ParamTooltip* tooltip = new ParamTooltip; | |||
tooltip->paramWidget = this; | |||
APP->scene->addChild(tooltip); | |||
this->tooltip = tooltip; | |||
} | |||
} | |||
@@ -207,7 +220,7 @@ void ParamWidget::createContextMenu() { | |||
// fineItem->disabled = true; | |||
// menu->addChild(fineItem); | |||
engine::ParamHandle* paramHandle = paramQuantity ? APP->engine->getParamHandle(paramQuantity->module->id, paramQuantity->paramId) : NULL; | |||
engine::ParamHandle* paramHandle = module ? APP->engine->getParamHandle(module->id, paramId) : NULL; | |||
if (paramHandle) { | |||
ParamUnmapItem* unmapItem = new ParamUnmapItem; | |||
unmapItem->text = "Unmap"; | |||
@@ -218,17 +231,18 @@ void ParamWidget::createContextMenu() { | |||
} | |||
void ParamWidget::resetAction() { | |||
if (paramQuantity && paramQuantity->resetEnabled) { | |||
float oldValue = paramQuantity->getValue(); | |||
paramQuantity->reset(); | |||
float newValue = paramQuantity->getValue(); | |||
engine::ParamQuantity* pq = getParamQuantity(); | |||
if (pq && pq->resetEnabled) { | |||
float oldValue = pq->getValue(); | |||
pq->reset(); | |||
float newValue = pq->getValue(); | |||
if (oldValue != newValue) { | |||
// Push ParamChange history action | |||
history::ParamChange* h = new history::ParamChange; | |||
h->name = "reset parameter"; | |||
h->moduleId = paramQuantity->module->id; | |||
h->paramId = paramQuantity->paramId; | |||
h->moduleId = module->id; | |||
h->paramId = paramId; | |||
h->oldValue = oldValue; | |||
h->newValue = newValue; | |||
APP->history->push(h); | |||
@@ -3,6 +3,8 @@ | |||
#include <window.hpp> | |||
#include <app.hpp> | |||
#include <history.hpp> | |||
#include <engine/Engine.hpp> | |||
#include <settings.hpp> | |||
#include <componentlibrary.hpp> | |||
@@ -10,6 +12,42 @@ namespace rack { | |||
namespace app { | |||
struct PortTooltip : ui::Tooltip { | |||
PortWidget* portWidget; | |||
void step() override { | |||
if (portWidget->module) { | |||
engine::Port* port = portWidget->getPort(); | |||
engine::PortInfo* portInfo = portWidget->getPortInfo(); | |||
// Label | |||
text = portInfo->label; | |||
// Voltage, number of channels | |||
int channels = port->getChannels(); | |||
for (int i = 0; i < channels; i++) { | |||
// Add newline or comma | |||
if (i % 4 == 0) | |||
text += "\n"; | |||
else | |||
text += " "; | |||
text += string::f("%5gV", port->getVoltage(i)); | |||
} | |||
// Description | |||
std::string description = portInfo->description; | |||
if (description != "") { | |||
text += "\n"; | |||
text += description; | |||
} | |||
} | |||
Tooltip::step(); | |||
// Position at bottom-right of parameter | |||
box.pos = portWidget->getAbsoluteOffset(portWidget->box.size).round(); | |||
// Fit inside parent (copied from Tooltip.cpp) | |||
assert(parent); | |||
box = box.nudge(parent->box.zeroPos()); | |||
} | |||
}; | |||
struct PlugLight : MultiLightWidget { | |||
PlugLight() { | |||
addBaseColor(componentlibrary::SCHEME_GREEN); | |||
@@ -33,13 +71,48 @@ PortWidget::~PortWidget() { | |||
APP->scene->rack->clearCablesOnPort(this); | |||
} | |||
engine::Port* PortWidget::getPort() { | |||
if (!module) | |||
return NULL; | |||
if (type == engine::Port::INPUT) | |||
return &module->inputs[portId]; | |||
else | |||
return &module->outputs[portId]; | |||
} | |||
engine::PortInfo* PortWidget::getPortInfo() { | |||
if (!module) | |||
return NULL; | |||
if (type == engine::Port::INPUT) | |||
return module->inputInfos[portId]; | |||
else | |||
return module->outputInfos[portId]; | |||
} | |||
void PortWidget::createTooltip() { | |||
if (settings::paramTooltip && !this->tooltip && module) { | |||
PortTooltip* tooltip = new PortTooltip; | |||
tooltip->portWidget = this; | |||
APP->scene->addChild(tooltip); | |||
this->tooltip = tooltip; | |||
} | |||
} | |||
void PortWidget::destroyTooltip() { | |||
if (tooltip) { | |||
APP->scene->removeChild(tooltip); | |||
delete tooltip; | |||
tooltip = NULL; | |||
} | |||
} | |||
void PortWidget::step() { | |||
if (!module) | |||
return; | |||
std::vector<float> values(3); | |||
for (int i = 0; i < 3; i++) { | |||
if (type == OUTPUT) | |||
if (type == engine::Port::OUTPUT) | |||
values[i] = module->outputs[portId].plugLights[i].getBrightness(); | |||
else | |||
values[i] = module->inputs[portId].plugLights[i].getBrightness(); | |||
@@ -53,7 +126,7 @@ void PortWidget::draw(const DrawArgs& args) { | |||
CableWidget* cw = APP->scene->rack->incompleteCable; | |||
if (cw) { | |||
// Dim the PortWidget if the active cable cannot plug into this PortWidget | |||
if (type == OUTPUT ? cw->outputPort : cw->inputPort) | |||
if (type == engine::Port::OUTPUT ? cw->outputPort : cw->inputPort) | |||
nvgGlobalAlpha(args.vg, 0.5); | |||
} | |||
Widget::draw(args); | |||
@@ -80,10 +153,12 @@ void PortWidget::onButton(const event::Button& e) { | |||
void PortWidget::onEnter(const event::Enter& e) { | |||
hovered = true; | |||
createTooltip(); | |||
} | |||
void PortWidget::onLeave(const event::Leave& e) { | |||
hovered = false; | |||
destroyTooltip(); | |||
} | |||
void PortWidget::onDragStart(const event::DragStart& e) { | |||
@@ -92,7 +167,7 @@ void PortWidget::onDragStart(const event::DragStart& e) { | |||
CableWidget* cw = NULL; | |||
if ((APP->window->getMods() & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
if (type == OUTPUT) { | |||
if (type == engine::Port::OUTPUT) { | |||
// Ctrl-clicking an output creates a new cable. | |||
// Keep cable NULL. Will be created below | |||
} | |||
@@ -118,7 +193,7 @@ void PortWidget::onDragStart(const event::DragStart& e) { | |||
// Disconnect and reuse existing cable | |||
APP->scene->rack->removeCable(cw); | |||
if (type == OUTPUT) | |||
if (type == engine::Port::OUTPUT) | |||
cw->outputPort = NULL; | |||
else | |||
cw->inputPort = NULL; | |||
@@ -129,7 +204,7 @@ void PortWidget::onDragStart(const event::DragStart& e) { | |||
if (!cw) { | |||
// Create a new cable | |||
cw = new CableWidget; | |||
if (type == OUTPUT) | |||
if (type == engine::Port::OUTPUT) | |||
cw->outputPort = this; | |||
else | |||
cw->inputPort = this; | |||
@@ -165,7 +240,7 @@ void PortWidget::onDragDrop(const event::DragDrop& e) { | |||
return; | |||
// Reject ports if this is an input port and something is already plugged into it | |||
if (type == INPUT) { | |||
if (type == engine::Port::INPUT) { | |||
if (APP->scene->rack->getTopCable(this)) | |||
return; | |||
} | |||
@@ -173,7 +248,7 @@ void PortWidget::onDragDrop(const event::DragDrop& e) { | |||
CableWidget* cw = APP->scene->rack->incompleteCable; | |||
if (cw) { | |||
cw->hoveredOutputPort = cw->hoveredInputPort = NULL; | |||
if (type == OUTPUT) | |||
if (type == engine::Port::OUTPUT) | |||
cw->outputPort = this; | |||
else | |||
cw->inputPort = this; | |||
@@ -182,18 +257,19 @@ void PortWidget::onDragDrop(const event::DragDrop& e) { | |||
} | |||
void PortWidget::onDragEnter(const event::DragEnter& e) { | |||
createTooltip(); | |||
if (e.button != GLFW_MOUSE_BUTTON_LEFT) | |||
return; | |||
// Reject ports if this is an input port and something is already plugged into it | |||
if (type == INPUT) { | |||
if (type == engine::Port::INPUT) { | |||
if (APP->scene->rack->getTopCable(this)) | |||
return; | |||
} | |||
CableWidget* cw = APP->scene->rack->incompleteCable; | |||
if (cw) { | |||
if (type == OUTPUT) | |||
if (type == engine::Port::OUTPUT) | |||
cw->hoveredOutputPort = this; | |||
else | |||
cw->hoveredInputPort = this; | |||
@@ -201,6 +277,7 @@ void PortWidget::onDragEnter(const event::DragEnter& e) { | |||
} | |||
void PortWidget::onDragLeave(const event::DragLeave& e) { | |||
destroyTooltip(); | |||
if (e.button != GLFW_MOUSE_BUTTON_LEFT) | |||
return; | |||
@@ -210,7 +287,7 @@ void PortWidget::onDragLeave(const event::DragLeave& e) { | |||
CableWidget* cw = APP->scene->rack->incompleteCable; | |||
if (cw) { | |||
if (type == OUTPUT) | |||
if (type == engine::Port::OUTPUT) | |||
cw->hoveredOutputPort = NULL; | |||
else | |||
cw->hoveredInputPort = NULL; | |||
@@ -33,10 +33,11 @@ void SvgKnob::setSvg(std::shared_ptr<Svg> svg) { | |||
void SvgKnob::onChange(const event::Change& e) { | |||
// Re-transform the widget::TransformWidget | |||
if (paramQuantity) { | |||
float value = paramQuantity->getScaledValue(); | |||
engine::ParamQuantity* pq = getParamQuantity(); | |||
if (pq) { | |||
float value = pq->getScaledValue(); | |||
float angle; | |||
if (paramQuantity->isBounded()) { | |||
if (pq->isBounded()) { | |||
angle = math::rescale(value, 0.f, 1.f, minAngle, maxAngle); | |||
} | |||
else { | |||
@@ -31,9 +31,10 @@ void SvgSlider::setHandleSvg(std::shared_ptr<Svg> svg) { | |||
} | |||
void SvgSlider::onChange(const event::Change& e) { | |||
if (paramQuantity) { | |||
engine::ParamQuantity* pq = getParamQuantity(); | |||
if (pq) { | |||
// Interpolate handle position | |||
float v = paramQuantity->getScaledValue(); | |||
float v = pq->getScaledValue(); | |||
handle->box.pos = math::Vec( | |||
math::rescale(v, 0.f, 1.f, minHandlePos.x, maxHandlePos.x), | |||
math::rescale(v, 0.f, 1.f, minHandlePos.y, maxHandlePos.y)); | |||
@@ -31,8 +31,9 @@ void SvgSwitch::addFrame(std::shared_ptr<Svg> svg) { | |||
} | |||
void SvgSwitch::onChange(const event::Change& e) { | |||
if (!frames.empty() && paramQuantity) { | |||
int index = (int) std::round(paramQuantity->getValue() - paramQuantity->getMinValue()); | |||
engine::ParamQuantity* pq = getParamQuantity(); | |||
if (!frames.empty() && pq) { | |||
int index = (int) std::round(pq->getValue() - pq->getMinValue()); | |||
index = math::clamp(index, 0, (int) frames.size() - 1); | |||
sw->setSvg(frames[index]); | |||
fb->dirty = true; | |||
@@ -11,25 +11,27 @@ namespace app { | |||
void Switch::init() { | |||
ParamWidget::init(); | |||
if (paramQuantity) { | |||
paramQuantity->snapEnabled = true; | |||
engine::ParamQuantity* pq = getParamQuantity(); | |||
if (pq) { | |||
pq->snapEnabled = true; | |||
if (momentary) { | |||
paramQuantity->resetEnabled = false; | |||
paramQuantity->randomizeEnabled = false; | |||
pq->resetEnabled = false; | |||
pq->randomizeEnabled = false; | |||
} | |||
} | |||
} | |||
void Switch::step() { | |||
engine::ParamQuantity* pq = getParamQuantity(); | |||
if (momentaryPressed) { | |||
momentaryPressed = false; | |||
// Wait another frame. | |||
} | |||
else if (momentaryReleased) { | |||
momentaryReleased = false; | |||
if (paramQuantity) { | |||
if (pq) { | |||
// Set to minimum value | |||
paramQuantity->setMin(); | |||
pq->setMin(); | |||
} | |||
} | |||
ParamWidget::step(); | |||
@@ -43,32 +45,33 @@ void Switch::onDragStart(const event::DragStart& e) { | |||
if (e.button != GLFW_MOUSE_BUTTON_LEFT) | |||
return; | |||
engine::ParamQuantity* pq = getParamQuantity(); | |||
if (momentary) { | |||
if (paramQuantity) { | |||
if (pq) { | |||
// Set to maximum value | |||
paramQuantity->setMax(); | |||
pq->setMax(); | |||
momentaryPressed = true; | |||
} | |||
} | |||
else { | |||
if (paramQuantity) { | |||
float oldValue = paramQuantity->getValue(); | |||
if (paramQuantity->isMax()) { | |||
if (pq) { | |||
float oldValue = pq->getValue(); | |||
if (pq->isMax()) { | |||
// Reset value back to minimum | |||
paramQuantity->setMin(); | |||
pq->setMin(); | |||
} | |||
else { | |||
// Increment value by 1 | |||
paramQuantity->setValue(std::round(paramQuantity->getValue()) + 1.f); | |||
pq->setValue(std::round(pq->getValue()) + 1.f); | |||
} | |||
float newValue = paramQuantity->getValue(); | |||
float newValue = pq->getValue(); | |||
if (oldValue != newValue) { | |||
// Push ParamChange history action | |||
history::ParamChange* h = new history::ParamChange; | |||
h->name = "move switch"; | |||
h->moduleId = paramQuantity->module->id; | |||
h->paramId = paramQuantity->paramId; | |||
h->moduleId = module->id; | |||
h->paramId = paramId; | |||
h->oldValue = oldValue; | |||
h->newValue = newValue; | |||
APP->history->push(h); | |||
@@ -72,6 +72,18 @@ struct MIDI_CV : Module { | |||
MIDI_CV() { | |||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
configOutput(CV_OUTPUT, "V/oct"); | |||
configOutput(GATE_OUTPUT, "Gate"); | |||
configOutput(VELOCITY_OUTPUT, "Velocity"); | |||
configOutput(AFTERTOUCH_OUTPUT, "Aftertouch"); | |||
configOutput(PITCH_OUTPUT, "Pitch wheel"); | |||
configOutput(MOD_OUTPUT, "Mod wheel"); | |||
configOutput(RETRIGGER_OUTPUT, "Retrigger"); | |||
configOutput(CLOCK_OUTPUT, "Clock"); | |||
configOutput(CLOCK_DIV_OUTPUT, "Clock divider"); | |||
configOutput(START_OUTPUT, "Start"); | |||
configOutput(STOP_OUTPUT, "Stop"); | |||
configOutput(CONTINUE_OUTPUT, "Continue"); | |||
heldNotes.reserve(128); | |||
for (int c = 0; c < 16; c++) { | |||
pitchFilters[c].setTau(1 / 30.f); | |||
@@ -321,8 +321,8 @@ struct MIDI_MapChoice : LedDisplayChoice { | |||
ParamWidget* touchedParam = APP->scene->rack->touchedParam; | |||
if (touchedParam) { | |||
APP->scene->rack->touchedParam = NULL; | |||
int moduleId = touchedParam->paramQuantity->module->id; | |||
int paramId = touchedParam->paramQuantity->paramId; | |||
int moduleId = touchedParam->module->id; | |||
int paramId = touchedParam->paramId; | |||
module->learnParam(id, moduleId, paramId); | |||
} | |||
else { | |||
@@ -555,8 +555,8 @@ void Engine::removeModule(Module* module) { | |||
} | |||
// Check that all cables are disconnected | |||
for (Cable* cable : internal->cables) { | |||
assert(cable->outputModule != module); | |||
assert(cable->inputModule != module); | |||
assert(cable->outputModule != module); | |||
} | |||
// Update ParamHandles' module pointers | |||
for (ParamHandle* paramHandle : internal->paramHandles) { | |||
@@ -657,26 +657,26 @@ static void Engine_updateConnected(Engine* that) { | |||
// Find disconnected ports | |||
std::set<Port*> disconnectedPorts; | |||
for (Module* module : that->internal->modules) { | |||
for (Output& output : module->outputs) { | |||
disconnectedPorts.insert(&output); | |||
} | |||
for (Input& input : module->inputs) { | |||
disconnectedPorts.insert(&input); | |||
} | |||
for (Output& output : module->outputs) { | |||
disconnectedPorts.insert(&output); | |||
} | |||
} | |||
for (Cable* cable : that->internal->cables) { | |||
// Connect output | |||
Output& output = cable->outputModule->outputs[cable->outputId]; | |||
auto outputIt = disconnectedPorts.find(&output); | |||
if (outputIt != disconnectedPorts.end()) | |||
disconnectedPorts.erase(outputIt); | |||
Port_setConnected(&output); | |||
// Connect input | |||
Input& input = cable->inputModule->inputs[cable->inputId]; | |||
auto inputIt = disconnectedPorts.find(&input); | |||
if (inputIt != disconnectedPorts.end()) | |||
disconnectedPorts.erase(inputIt); | |||
Port_setConnected(&input); | |||
// Connect output | |||
Output& output = cable->outputModule->outputs[cable->outputId]; | |||
auto outputIt = disconnectedPorts.find(&output); | |||
if (outputIt != disconnectedPorts.end()) | |||
disconnectedPorts.erase(outputIt); | |||
Port_setConnected(&output); | |||
} | |||
// Disconnect ports that have no cable | |||
for (Port* port : disconnectedPorts) { | |||
@@ -14,6 +14,14 @@ Module::~Module() { | |||
if (paramQuantity) | |||
delete paramQuantity; | |||
} | |||
for (PortInfo* inputInfo : inputInfos) { | |||
if (inputInfo) | |||
delete inputInfo; | |||
} | |||
for (PortInfo* outputInfo : outputInfos) { | |||
if (outputInfo) | |||
delete outputInfo; | |||
} | |||
} | |||
void Module::config(int numParams, int numInputs, int numOutputs, int numLights) { | |||
@@ -23,11 +31,20 @@ void Module::config(int numParams, int numInputs, int numOutputs, int numLights) | |||
inputs.resize(numInputs); | |||
outputs.resize(numOutputs); | |||
lights.resize(numLights); | |||
paramQuantities.resize(numParams); | |||
// Initialize paramQuantities | |||
paramQuantities.resize(numParams); | |||
for (int i = 0; i < numParams; i++) { | |||
configParam(i, 0.f, 1.f, 0.f); | |||
} | |||
// Initialize PortInfos | |||
inputInfos.resize(numInputs); | |||
for (int i = 0; i < numInputs; i++) { | |||
configInput(i); | |||
} | |||
outputInfos.resize(numOutputs); | |||
for (int i = 0; i < numOutputs; i++) { | |||
configOutput(i); | |||
} | |||
} | |||
json_t* Module::toJson() { | |||
@@ -118,16 +118,21 @@ void State::finalizeWidget(widget::Widget* w) { | |||
} | |||
bool State::handleButton(math::Vec pos, int button, int action, int mods) { | |||
// Button | |||
Context cButton; | |||
Button eButton; | |||
eButton.context = &cButton; | |||
eButton.pos = pos; | |||
eButton.button = button; | |||
eButton.action = action; | |||
eButton.mods = mods; | |||
rootWidget->onButton(eButton); | |||
widget::Widget* clickedWidget = cButton.target; | |||
bool cursorLocked = APP->window->isCursorLocked(); | |||
widget::Widget* clickedWidget = NULL; | |||
if (!cursorLocked) { | |||
// Button | |||
Context cButton; | |||
Button eButton; | |||
eButton.context = &cButton; | |||
eButton.pos = pos; | |||
eButton.button = button; | |||
eButton.action = action; | |||
eButton.mods = mods; | |||
rootWidget->onButton(eButton); | |||
clickedWidget = cButton.target; | |||
} | |||
if (action == GLFW_PRESS) { | |||
setDragged(clickedWidget, button); | |||
@@ -176,11 +181,15 @@ bool State::handleButton(math::Vec pos, int button, int action, int mods) { | |||
} | |||
bool State::handleHover(math::Vec pos, math::Vec mouseDelta) { | |||
bool cursorLocked = APP->window->isCursorLocked(); | |||
// Fake a key RACK_HELD event for each held key | |||
int mods = APP->window->getMods(); | |||
for (int key : heldKeys) { | |||
int scancode = glfwGetKeyScancode(key); | |||
handleKey(pos, key, scancode, RACK_HELD, mods); | |||
if (!cursorLocked) { | |||
int mods = APP->window->getMods(); | |||
for (int key : heldKeys) { | |||
int scancode = glfwGetKeyScancode(key); | |||
handleKey(pos, key, scancode, RACK_HELD, mods); | |||
} | |||
} | |||
if (draggedWidget) { | |||
@@ -190,31 +199,36 @@ bool State::handleHover(math::Vec pos, math::Vec mouseDelta) { | |||
eDragMove.mouseDelta = mouseDelta; | |||
draggedWidget->onDragMove(eDragMove); | |||
// DragHover | |||
Context cDragHover; | |||
DragHover eDragHover; | |||
eDragHover.context = &cDragHover; | |||
eDragHover.button = dragButton; | |||
eDragHover.pos = pos; | |||
eDragHover.mouseDelta = mouseDelta; | |||
eDragHover.origin = draggedWidget; | |||
rootWidget->onDragHover(eDragHover); | |||
setDragHovered(cDragHover.target); | |||
if (cDragHover.target) | |||
return true; | |||
if (!cursorLocked) { | |||
// DragHover | |||
Context cDragHover; | |||
DragHover eDragHover; | |||
eDragHover.context = &cDragHover; | |||
eDragHover.button = dragButton; | |||
eDragHover.pos = pos; | |||
eDragHover.mouseDelta = mouseDelta; | |||
eDragHover.origin = draggedWidget; | |||
rootWidget->onDragHover(eDragHover); | |||
setDragHovered(cDragHover.target); | |||
if (cDragHover.target) | |||
return true; | |||
} | |||
} | |||
// Hover | |||
Context cHover; | |||
Hover eHover; | |||
eHover.context = &cHover; | |||
eHover.pos = pos; | |||
eHover.mouseDelta = mouseDelta; | |||
rootWidget->onHover(eHover); | |||
setHovered(cHover.target); | |||
return !!cHover.target; | |||
if (!cursorLocked) { | |||
// Hover | |||
Context cHover; | |||
Hover eHover; | |||
eHover.context = &cHover; | |||
eHover.pos = pos; | |||
eHover.mouseDelta = mouseDelta; | |||
rootWidget->onHover(eHover); | |||
setHovered(cHover.target); | |||
return !!cHover.target; | |||
} | |||
return false; | |||
} | |||
bool State::handleLeave() { | |||
@@ -492,6 +492,10 @@ void Window::cursorUnlock() { | |||
} | |||
} | |||
bool Window::isCursorLocked() { | |||
return glfwGetInputMode(win, GLFW_CURSOR) != GLFW_CURSOR_NORMAL; | |||
} | |||
int Window::getMods() { | |||
int mods = 0; | |||
if (glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS) | |||