diff --git a/include/app/ModuleLightWidget.hpp b/include/app/ModuleLightWidget.hpp index 2e2ab13d..73e19051 100644 --- a/include/app/ModuleLightWidget.hpp +++ b/include/app/ModuleLightWidget.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include @@ -15,7 +16,18 @@ struct ModuleLightWidget : MultiLightWidget { engine::Module* module = NULL; int firstLightId; + ui::Tooltip* tooltip = NULL; + + ~ModuleLightWidget(); + engine::Light* getLight(int colorId); + engine::LightInfo* getLightInfo(); + void createTooltip(); + void destroyTooltip(); + void step() override; + void onHover(const event::Hover& e) override; + void onEnter(const event::Enter& e) override; + void onLeave(const event::Leave& e) override; }; diff --git a/include/app/MultiLightWidget.hpp b/include/app/MultiLightWidget.hpp index 9fb2dc58..941ae749 100644 --- a/include/app/MultiLightWidget.hpp +++ b/include/app/MultiLightWidget.hpp @@ -12,6 +12,7 @@ struct MultiLightWidget : LightWidget { /** Colors of each value state */ std::vector baseColors; + int getNumColors(); void addBaseColor(NVGcolor baseColor); /** Sets the color to a linear combination of the baseColors with the given weights */ void setBrightnesses(const std::vector& brightnesses); diff --git a/include/app/PortWidget.hpp b/include/app/PortWidget.hpp index 82f039d5..ef3e90b7 100644 --- a/include/app/PortWidget.hpp +++ b/include/app/PortWidget.hpp @@ -18,7 +18,6 @@ struct PortWidget : widget::OpaqueWidget { int portId; ui::Tooltip* tooltip = NULL; - bool hovered = false; MultiLightWidget* plugLight; diff --git a/include/engine/LightInfo.hpp b/include/engine/LightInfo.hpp new file mode 100644 index 00000000..12889585 --- /dev/null +++ b/include/engine/LightInfo.hpp @@ -0,0 +1,34 @@ +#pragma once +#include + + +namespace rack { +namespace engine { + + +struct Module; + + +struct LightInfo { + Module* module = NULL; + int lightId; + + /** The name of the light, using sentence capitalization. + e.g. "Level", "Pitch light", "Mode CV". + + Don't use the word "light" or "LED" in the name. + Since this text is often prepended or appended to the name, the name will appear as e.g. "Level light light", "Light: Level light". + */ + std::string name; + + /** An optional one-sentence description of the light. */ + std::string description; + + virtual ~LightInfo() {} + virtual std::string getName(); + virtual std::string getDescription(); +}; + + +} // namespace engine +} // namespace rack diff --git a/include/engine/Module.hpp b/include/engine/Module.hpp index 6b1644dd..bb53d75e 100644 --- a/include/engine/Module.hpp +++ b/include/engine/Module.hpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace rack { @@ -43,9 +44,15 @@ struct Module { std::vector inputs; std::vector outputs; std::vector lights; + + /** Arrays of components. + Initialized with configParam(), configInput(), configOutput(), and configLight(). + LightInfos are initialized to null unless configLight() is called. + */ std::vector paramQuantities; std::vector inputInfos; std::vector outputInfos; + std::vector lightInfos; /** Represents a message-passing channel for an adjacent module. */ struct Expander { @@ -150,13 +157,13 @@ struct Module { if (inputInfos[portId]) delete inputInfos[portId]; - TPortInfo* p = new TPortInfo; - p->module = this; - p->type = Port::INPUT; - p->portId = portId; - p->name = name; - inputInfos[portId] = p; - return p; + TPortInfo* info = new TPortInfo; + info->module = this; + info->type = Port::INPUT; + info->portId = portId; + info->name = name; + inputInfos[portId] = info; + return info; } /** Helper for creating a PortInfo for an output port and setting its properties. @@ -168,13 +175,31 @@ struct Module { if (outputInfos[portId]) delete outputInfos[portId]; - TPortInfo* p = new TPortInfo; - p->module = this; - p->type = Port::OUTPUT; - p->portId = portId; - p->name = name; - outputInfos[portId] = p; - return p; + TPortInfo* info = new TPortInfo; + info->module = this; + info->type = Port::OUTPUT; + info->portId = portId; + info->name = name; + outputInfos[portId] = info; + return info; + } + + /** Helper for creating a LightInfo and setting its properties. + For multi-colored lights, use the first lightId. + See LightInfo for documentation of arguments. + */ + template + TLightInfo* configLight(int lightId, std::string name = "") { + assert(lightId < (int) lights.size() && lightId < (int) lightInfos.size()); + if (lightInfos[lightId]) + delete lightInfos[lightId]; + + TLightInfo* info = new TLightInfo; + info->module = this; + info->lightId = lightId; + info->name = name; + lightInfos[lightId] = info; + return info; } /** Adds a direct route from an input to an output when the module is bypassed. diff --git a/include/settings.hpp b/include/settings.hpp index 2254bb4d..46189a15 100644 --- a/include/settings.hpp +++ b/include/settings.hpp @@ -42,7 +42,7 @@ extern KnobMode knobMode; extern float knobLinearSensitivity; extern float sampleRate; extern int threadCount; -extern bool paramTooltip; +extern bool tooltips; extern bool cpuMeter; extern bool lockModules; extern int frameSwapInterval; diff --git a/src/app/CableWidget.cpp b/src/app/CableWidget.cpp index b3e131de..7eaba94e 100644 --- a/src/app/CableWidget.cpp +++ b/src/app/CableWidget.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -189,17 +190,18 @@ void CableWidget::draw(const DrawArgs& args) { if (isComplete()) { engine::Output* output = &cable->outputModule->outputs[cable->outputId]; - // Draw opaque if mouse is hovering over a connected port + // Increase thickness if output port is polyphonic if (output->channels > 1) { - // Increase thickness if output port is polyphonic thickness = 9; } - if (outputPort->hovered || inputPort->hovered) { + // Draw opaque if mouse is hovering over a connected port + Widget* hoveredWidget = APP->event->hoveredWidget; + if (outputPort == hoveredWidget || inputPort == hoveredWidget) { opacity = 1.0; } + // Draw translucent cable if not active (i.e. 0 channels) else if (output->channels == 0) { - // Draw translucent cable if not active (i.e. 0 channels) opacity *= 0.5; } } diff --git a/src/app/MenuBar.cpp b/src/app/MenuBar.cpp index 923265f4..ad3122a4 100644 --- a/src/app/MenuBar.cpp +++ b/src/app/MenuBar.cpp @@ -341,9 +341,9 @@ struct CableTensionSlider : ui::Slider { } }; -struct ParamTooltipItem : ui::MenuItem { +struct TooltipsItem : ui::MenuItem { void onAction(const event::Action& e) override { - settings::paramTooltip ^= true; + settings::tooltips ^= true; } }; @@ -423,10 +423,10 @@ struct ViewButton : MenuButton { menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); menu->box.size.x = box.size.x; - ParamTooltipItem* paramTooltipItem = new ParamTooltipItem; - paramTooltipItem->text = "Show tooltips"; - paramTooltipItem->rightText = CHECKMARK(settings::paramTooltip); - menu->addChild(paramTooltipItem); + TooltipsItem* tooltipsItem = new TooltipsItem; + tooltipsItem->text = "Show tooltips"; + tooltipsItem->rightText = CHECKMARK(settings::tooltips); + menu->addChild(tooltipsItem); AllowCursorLockItem* allowCursorLockItem = new AllowCursorLockItem; allowCursorLockItem->text = "Lock cursor when dragging parameters"; diff --git a/src/app/ModuleLightWidget.cpp b/src/app/ModuleLightWidget.cpp index 607af9a0..2a99cb59 100644 --- a/src/app/ModuleLightWidget.cpp +++ b/src/app/ModuleLightWidget.cpp @@ -1,10 +1,96 @@ #include +#include +#include +#include namespace rack { namespace app { +struct LightTooltip : ui::Tooltip { + ModuleLightWidget* lightWidget; + + void step() override { + if (lightWidget->module) { + engine::LightInfo* lightInfo = lightWidget->getLightInfo(); + if (!lightInfo) + return; + // Label + text = lightInfo->getName(); + text += " light"; + // Description + std::string description = lightInfo->getDescription(); + if (description != "") { + text += "\n"; + text += description; + } + // Brightness for each color + text += "\n"; + int numColors = lightWidget->getNumColors(); + for (int colorId = 0; colorId < numColors; colorId++) { + if (colorId > 1) + text += " "; + engine::Light* light = lightWidget->getLight(colorId); + float brightness = math::clamp(light->getBrightness(), 0.f, 1.f); + text += string::f("% 3.0f%%", brightness * 100.f); + } + } + Tooltip::step(); + // Position at bottom-right of parameter + box.pos = lightWidget->getAbsoluteOffset(lightWidget->box.size).round(); + // Fit inside parent (copied from Tooltip.cpp) + assert(parent); + box = box.nudge(parent->box.zeroPos()); + } +}; + + +ModuleLightWidget::~ModuleLightWidget() { + destroyTooltip(); +} + + +engine::Light* ModuleLightWidget::getLight(int colorId) { + if (!module) + return NULL; + return &module->lights[firstLightId + colorId]; +} + + +engine::LightInfo* ModuleLightWidget::getLightInfo() { + if (!module) + return NULL; + return module->lightInfos[firstLightId]; +} + + +void ModuleLightWidget::createTooltip() { + if (!settings::tooltips) + return; + if (this->tooltip) + return; + if (!module) + return; + // If the LightInfo is null, don't show a tooltip + if (!getLightInfo()) + return; + LightTooltip* tooltip = new LightTooltip; + tooltip->lightWidget = this; + APP->scene->addChild(tooltip); + this->tooltip = tooltip; +} + + +void ModuleLightWidget::destroyTooltip() { + if (!tooltip) + return; + APP->scene->removeChild(tooltip); + delete tooltip; + tooltip = NULL; +} + + void ModuleLightWidget::step() { std::vector brightnesses(baseColors.size()); @@ -32,5 +118,25 @@ void ModuleLightWidget::step() { } +void ModuleLightWidget::onHover(const event::Hover& e) { + // Adapted from OpaqueWidget::onHover() + Widget::onHover(e); + e.stopPropagating(); + // Consume if not consumed by child + if (!e.isConsumed()) + e.consume(this); +} + + +void ModuleLightWidget::onEnter(const event::Enter& e) { + createTooltip(); +} + + +void ModuleLightWidget::onLeave(const event::Leave& e) { + destroyTooltip(); +} + + } // namespace app } // namespace rack diff --git a/src/app/MultiLightWidget.cpp b/src/app/MultiLightWidget.cpp index 1efca431..aaa78763 100644 --- a/src/app/MultiLightWidget.cpp +++ b/src/app/MultiLightWidget.cpp @@ -6,6 +6,11 @@ namespace rack { namespace app { +int MultiLightWidget::getNumColors() { + return baseColors.size(); +} + + void MultiLightWidget::addBaseColor(NVGcolor baseColor) { baseColors.push_back(baseColor); } diff --git a/src/app/ParamWidget.cpp b/src/app/ParamWidget.cpp index 870b8de4..4dcb5e17 100644 --- a/src/app/ParamWidget.cpp +++ b/src/app/ParamWidget.cpp @@ -152,20 +152,24 @@ engine::ParamQuantity* ParamWidget::getParamQuantity() { } void ParamWidget::createTooltip() { - if (settings::paramTooltip && !this->tooltip && module) { - ParamTooltip* tooltip = new ParamTooltip; - tooltip->paramWidget = this; - APP->scene->addChild(tooltip); - this->tooltip = tooltip; - } + if (!settings::tooltips) + return; + if (this->tooltip) + return; + if (!module) + return; + ParamTooltip* tooltip = new ParamTooltip; + tooltip->paramWidget = this; + APP->scene->addChild(tooltip); + this->tooltip = tooltip; } void ParamWidget::destroyTooltip() { - if (tooltip) { - APP->scene->removeChild(tooltip); - delete tooltip; - tooltip = NULL; - } + if (!tooltip) + return; + APP->scene->removeChild(tooltip); + delete tooltip; + tooltip = NULL; } void ParamWidget::step() { diff --git a/src/app/PortWidget.cpp b/src/app/PortWidget.cpp index 648d8ffa..4c880ade 100644 --- a/src/app/PortWidget.cpp +++ b/src/app/PortWidget.cpp @@ -107,20 +107,24 @@ engine::PortInfo* PortWidget::getPortInfo() { } void PortWidget::createTooltip() { - if (settings::paramTooltip && !this->tooltip && module) { - PortTooltip* tooltip = new PortTooltip; - tooltip->portWidget = this; - APP->scene->addChild(tooltip); - this->tooltip = tooltip; - } + if (!settings::tooltips) + return; + if (this->tooltip) + return; + if (!module) + return; + 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; - } + if (!tooltip) + return; + APP->scene->removeChild(tooltip); + delete tooltip; + tooltip = NULL; } void PortWidget::step() { @@ -169,12 +173,10 @@ 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(); } diff --git a/src/engine/LightInfo.cpp b/src/engine/LightInfo.cpp new file mode 100644 index 00000000..ef77bd70 --- /dev/null +++ b/src/engine/LightInfo.cpp @@ -0,0 +1,19 @@ +#include +#include + + +namespace rack { +namespace engine { + + +std::string LightInfo::getName() { + return name; +} + +std::string LightInfo::getDescription() { + return description; +} + + +} // namespace engine +} // namespace rack diff --git a/src/engine/Module.cpp b/src/engine/Module.cpp index 36062ae0..9d266d9c 100644 --- a/src/engine/Module.cpp +++ b/src/engine/Module.cpp @@ -34,6 +34,10 @@ Module::~Module() { if (outputInfo) delete outputInfo; } + for (LightInfo* lightInfo : lightInfos) { + if (lightInfo) + delete lightInfo; + } delete internal; } @@ -59,6 +63,8 @@ void Module::config(int numParams, int numInputs, int numOutputs, int numLights) for (int i = 0; i < numOutputs; i++) { configOutput(i); } + // Initialize LightInfos with null + lightInfos.resize(numLights); } diff --git a/src/settings.cpp b/src/settings.cpp index 39636c40..e709bc6d 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -28,7 +28,7 @@ KnobMode knobMode = KNOB_MODE_LINEAR; float knobLinearSensitivity = 0.001f; float sampleRate = 44100.0; int threadCount = 1; -bool paramTooltip = true; +bool tooltips = true; bool cpuMeter = false; bool lockModules = false; #if defined ARCH_MAC @@ -82,7 +82,7 @@ json_t* toJson() { json_object_set_new(rootJ, "threadCount", json_integer(threadCount)); - json_object_set_new(rootJ, "paramTooltip", json_boolean(paramTooltip)); + json_object_set_new(rootJ, "tooltips", json_boolean(tooltips)); json_object_set_new(rootJ, "cpuMeter", json_boolean(cpuMeter)); @@ -180,9 +180,9 @@ void fromJson(json_t* rootJ) { if (threadCountJ) threadCount = json_integer_value(threadCountJ); - json_t* paramTooltipJ = json_object_get(rootJ, "paramTooltip"); - if (paramTooltipJ) - paramTooltip = json_boolean_value(paramTooltipJ); + json_t* tooltipsJ = json_object_get(rootJ, "tooltips"); + if (tooltipsJ) + tooltips = json_boolean_value(tooltipsJ); json_t* cpuMeterJ = json_object_get(rootJ, "cpuMeter"); if (cpuMeterJ)