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