- 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) | |||