diff --git a/include/app/ModuleWidget.hpp b/include/app/ModuleWidget.hpp index a2a8343e..f526a5f7 100644 --- a/include/app/ModuleWidget.hpp +++ b/include/app/ModuleWidget.hpp @@ -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(); diff --git a/include/app/ParamWidget.hpp b/include/app/ParamWidget.hpp index a2aa487a..bd72b976 100644 --- a/include/app/ParamWidget.hpp +++ b/include/app/ParamWidget.hpp @@ -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; diff --git a/include/app/PortWidget.hpp b/include/app/PortWidget.hpp index 6a5c90a0..6d20378c 100644 --- a/include/app/PortWidget.hpp +++ b/include/app/PortWidget.hpp @@ -1,8 +1,10 @@ #pragma once #include #include +#include #include #include +#include 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; diff --git a/include/componentlibrary.hpp b/include/componentlibrary.hpp index 888fa3fd..2850804e 100644 --- a/include/componentlibrary.hpp +++ b/include/componentlibrary.hpp @@ -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; } }; diff --git a/include/engine/Module.hpp b/include/engine/Module.hpp index 907f9688..a49d9874 100644 --- a/include/engine/Module.hpp +++ b/include/engine/Module.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -36,6 +37,8 @@ struct Module { std::vector outputs; std::vector lights; std::vector paramQuantities; + std::vector inputInfos; + std::vector 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; diff --git a/include/engine/PortInfo.hpp b/include/engine/PortInfo.hpp new file mode 100644 index 00000000..3f3af483 --- /dev/null +++ b/include/engine/PortInfo.hpp @@ -0,0 +1,21 @@ +#pragma once +#include +#include + + +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 diff --git a/include/helpers.hpp b/include/helpers.hpp index b89fd1da..3b4da9be 100644 --- a/include/helpers.hpp +++ b/include/helpers.hpp @@ -59,9 +59,8 @@ template 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; } diff --git a/include/window.hpp b/include/window.hpp index acb5b1ea..6052ae6c 100644 --- a/include/window.hpp +++ b/include/window.hpp @@ -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. */ diff --git a/src/app/Knob.cpp b/src/app/Knob.cpp index 33dfe4e7..47a61e07 100644 --- a/src/app/Knob.cpp +++ b/src/app/Knob.cpp @@ -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); } } diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index 7e70b785..29fd4070 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -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); diff --git a/src/app/ParamWidget.cpp b/src/app/ParamWidget.cpp index fdd9d09e..d283fa34 100644 --- a/src/app/ParamWidget.cpp +++ b/src/app/ParamWidget.cpp @@ -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); diff --git a/src/app/PortWidget.cpp b/src/app/PortWidget.cpp index dd158e18..da474a94 100644 --- a/src/app/PortWidget.cpp +++ b/src/app/PortWidget.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include @@ -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 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; diff --git a/src/app/SvgKnob.cpp b/src/app/SvgKnob.cpp index e5f26986..ad6095a8 100644 --- a/src/app/SvgKnob.cpp +++ b/src/app/SvgKnob.cpp @@ -33,10 +33,11 @@ void SvgKnob::setSvg(std::shared_ptr 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 { diff --git a/src/app/SvgSlider.cpp b/src/app/SvgSlider.cpp index 65949ff7..d612c573 100644 --- a/src/app/SvgSlider.cpp +++ b/src/app/SvgSlider.cpp @@ -31,9 +31,10 @@ void SvgSlider::setHandleSvg(std::shared_ptr 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)); diff --git a/src/app/SvgSwitch.cpp b/src/app/SvgSwitch.cpp index 7ed0627e..f0d07129 100644 --- a/src/app/SvgSwitch.cpp +++ b/src/app/SvgSwitch.cpp @@ -31,8 +31,9 @@ void SvgSwitch::addFrame(std::shared_ptr 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; diff --git a/src/app/Switch.cpp b/src/app/Switch.cpp index 5bc6d1ce..579adc5b 100644 --- a/src/app/Switch.cpp +++ b/src/app/Switch.cpp @@ -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); diff --git a/src/core/MIDI_CV.cpp b/src/core/MIDI_CV.cpp index e001432c..7733a7fc 100644 --- a/src/core/MIDI_CV.cpp +++ b/src/core/MIDI_CV.cpp @@ -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); diff --git a/src/core/MIDI_Map.cpp b/src/core/MIDI_Map.cpp index 5d703c94..f15ca5ab 100644 --- a/src/core/MIDI_Map.cpp +++ b/src/core/MIDI_Map.cpp @@ -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 { diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 123edddb..9d2eb4b9 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -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 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) { diff --git a/src/engine/Module.cpp b/src/engine/Module.cpp index 4fb051da..63b2be91 100644 --- a/src/engine/Module.cpp +++ b/src/engine/Module.cpp @@ -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() { diff --git a/src/event.cpp b/src/event.cpp index f2bc88ed..4c419031 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -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() { diff --git a/src/window.cpp b/src/window.cpp index a5660d55..baca921b 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -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)