| @@ -1,6 +1,7 @@ | |||||
| #pragma once | #pragma once | ||||
| #include <app/common.hpp> | #include <app/common.hpp> | ||||
| #include <app/MultiLightWidget.hpp> | #include <app/MultiLightWidget.hpp> | ||||
| #include <ui/Tooltip.hpp> | |||||
| #include <engine/Module.hpp> | #include <engine/Module.hpp> | ||||
| @@ -15,7 +16,18 @@ struct ModuleLightWidget : MultiLightWidget { | |||||
| engine::Module* module = NULL; | engine::Module* module = NULL; | ||||
| int firstLightId; | int firstLightId; | ||||
| ui::Tooltip* tooltip = NULL; | |||||
| ~ModuleLightWidget(); | |||||
| engine::Light* getLight(int colorId); | |||||
| engine::LightInfo* getLightInfo(); | |||||
| void createTooltip(); | |||||
| void destroyTooltip(); | |||||
| void step() override; | void step() override; | ||||
| void onHover(const event::Hover& e) override; | |||||
| void onEnter(const event::Enter& e) override; | |||||
| void onLeave(const event::Leave& e) override; | |||||
| }; | }; | ||||
| @@ -12,6 +12,7 @@ struct MultiLightWidget : LightWidget { | |||||
| /** Colors of each value state */ | /** Colors of each value state */ | ||||
| std::vector<NVGcolor> baseColors; | std::vector<NVGcolor> baseColors; | ||||
| int getNumColors(); | |||||
| void addBaseColor(NVGcolor baseColor); | void addBaseColor(NVGcolor baseColor); | ||||
| /** Sets the color to a linear combination of the baseColors with the given weights */ | /** Sets the color to a linear combination of the baseColors with the given weights */ | ||||
| void setBrightnesses(const std::vector<float>& brightnesses); | void setBrightnesses(const std::vector<float>& brightnesses); | ||||
| @@ -18,7 +18,6 @@ struct PortWidget : widget::OpaqueWidget { | |||||
| int portId; | int portId; | ||||
| ui::Tooltip* tooltip = NULL; | ui::Tooltip* tooltip = NULL; | ||||
| bool hovered = false; | |||||
| MultiLightWidget* plugLight; | MultiLightWidget* plugLight; | ||||
| @@ -0,0 +1,34 @@ | |||||
| #pragma once | |||||
| #include <common.hpp> | |||||
| 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 | |||||
| @@ -11,6 +11,7 @@ | |||||
| #include <engine/Light.hpp> | #include <engine/Light.hpp> | ||||
| #include <engine/ParamQuantity.hpp> | #include <engine/ParamQuantity.hpp> | ||||
| #include <engine/PortInfo.hpp> | #include <engine/PortInfo.hpp> | ||||
| #include <engine/LightInfo.hpp> | |||||
| namespace rack { | namespace rack { | ||||
| @@ -43,9 +44,15 @@ struct Module { | |||||
| std::vector<Input> inputs; | std::vector<Input> inputs; | ||||
| std::vector<Output> outputs; | std::vector<Output> outputs; | ||||
| std::vector<Light> lights; | std::vector<Light> lights; | ||||
| /** Arrays of components. | |||||
| Initialized with configParam(), configInput(), configOutput(), and configLight(). | |||||
| LightInfos are initialized to null unless configLight() is called. | |||||
| */ | |||||
| std::vector<ParamQuantity*> paramQuantities; | std::vector<ParamQuantity*> paramQuantities; | ||||
| std::vector<PortInfo*> inputInfos; | std::vector<PortInfo*> inputInfos; | ||||
| std::vector<PortInfo*> outputInfos; | std::vector<PortInfo*> outputInfos; | ||||
| std::vector<LightInfo*> lightInfos; | |||||
| /** Represents a message-passing channel for an adjacent module. */ | /** Represents a message-passing channel for an adjacent module. */ | ||||
| struct Expander { | struct Expander { | ||||
| @@ -150,13 +157,13 @@ struct Module { | |||||
| if (inputInfos[portId]) | if (inputInfos[portId]) | ||||
| delete 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. | /** Helper for creating a PortInfo for an output port and setting its properties. | ||||
| @@ -168,13 +175,31 @@ struct Module { | |||||
| if (outputInfos[portId]) | if (outputInfos[portId]) | ||||
| delete 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 <class TLightInfo = LightInfo> | |||||
| 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. | /** Adds a direct route from an input to an output when the module is bypassed. | ||||
| @@ -42,7 +42,7 @@ extern KnobMode knobMode; | |||||
| extern float knobLinearSensitivity; | extern float knobLinearSensitivity; | ||||
| extern float sampleRate; | extern float sampleRate; | ||||
| extern int threadCount; | extern int threadCount; | ||||
| extern bool paramTooltip; | |||||
| extern bool tooltips; | |||||
| extern bool cpuMeter; | extern bool cpuMeter; | ||||
| extern bool lockModules; | extern bool lockModules; | ||||
| extern int frameSwapInterval; | extern int frameSwapInterval; | ||||
| @@ -5,6 +5,7 @@ | |||||
| #include <context.hpp> | #include <context.hpp> | ||||
| #include <patch.hpp> | #include <patch.hpp> | ||||
| #include <settings.hpp> | #include <settings.hpp> | ||||
| #include <event.hpp> | |||||
| #include <engine/Engine.hpp> | #include <engine/Engine.hpp> | ||||
| #include <engine/Port.hpp> | #include <engine/Port.hpp> | ||||
| @@ -189,17 +190,18 @@ void CableWidget::draw(const DrawArgs& args) { | |||||
| if (isComplete()) { | if (isComplete()) { | ||||
| engine::Output* output = &cable->outputModule->outputs[cable->outputId]; | 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) { | if (output->channels > 1) { | ||||
| // Increase thickness if output port is polyphonic | |||||
| thickness = 9; | 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; | opacity = 1.0; | ||||
| } | } | ||||
| // Draw translucent cable if not active (i.e. 0 channels) | |||||
| else if (output->channels == 0) { | else if (output->channels == 0) { | ||||
| // Draw translucent cable if not active (i.e. 0 channels) | |||||
| opacity *= 0.5; | opacity *= 0.5; | ||||
| } | } | ||||
| } | } | ||||
| @@ -341,9 +341,9 @@ struct CableTensionSlider : ui::Slider { | |||||
| } | } | ||||
| }; | }; | ||||
| struct ParamTooltipItem : ui::MenuItem { | |||||
| struct TooltipsItem : ui::MenuItem { | |||||
| void onAction(const event::Action& e) override { | 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.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); | ||||
| menu->box.size.x = box.size.x; | 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* allowCursorLockItem = new AllowCursorLockItem; | ||||
| allowCursorLockItem->text = "Lock cursor when dragging parameters"; | allowCursorLockItem->text = "Lock cursor when dragging parameters"; | ||||
| @@ -1,10 +1,96 @@ | |||||
| #include <app/ModuleLightWidget.hpp> | #include <app/ModuleLightWidget.hpp> | ||||
| #include <app/Scene.hpp> | |||||
| #include <context.hpp> | |||||
| #include <settings.hpp> | |||||
| namespace rack { | namespace rack { | ||||
| namespace app { | 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() { | void ModuleLightWidget::step() { | ||||
| std::vector<float> brightnesses(baseColors.size()); | std::vector<float> 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 app | ||||
| } // namespace rack | } // namespace rack | ||||
| @@ -6,6 +6,11 @@ namespace rack { | |||||
| namespace app { | namespace app { | ||||
| int MultiLightWidget::getNumColors() { | |||||
| return baseColors.size(); | |||||
| } | |||||
| void MultiLightWidget::addBaseColor(NVGcolor baseColor) { | void MultiLightWidget::addBaseColor(NVGcolor baseColor) { | ||||
| baseColors.push_back(baseColor); | baseColors.push_back(baseColor); | ||||
| } | } | ||||
| @@ -152,20 +152,24 @@ engine::ParamQuantity* ParamWidget::getParamQuantity() { | |||||
| } | } | ||||
| void ParamWidget::createTooltip() { | 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() { | 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() { | void ParamWidget::step() { | ||||
| @@ -107,20 +107,24 @@ engine::PortInfo* PortWidget::getPortInfo() { | |||||
| } | } | ||||
| void PortWidget::createTooltip() { | 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() { | 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() { | void PortWidget::step() { | ||||
| @@ -169,12 +173,10 @@ void PortWidget::onButton(const event::Button& e) { | |||||
| } | } | ||||
| void PortWidget::onEnter(const event::Enter& e) { | void PortWidget::onEnter(const event::Enter& e) { | ||||
| hovered = true; | |||||
| createTooltip(); | createTooltip(); | ||||
| } | } | ||||
| void PortWidget::onLeave(const event::Leave& e) { | void PortWidget::onLeave(const event::Leave& e) { | ||||
| hovered = false; | |||||
| destroyTooltip(); | destroyTooltip(); | ||||
| } | } | ||||
| @@ -0,0 +1,19 @@ | |||||
| #include <engine/LightInfo.hpp> | |||||
| #include <string.hpp> | |||||
| namespace rack { | |||||
| namespace engine { | |||||
| std::string LightInfo::getName() { | |||||
| return name; | |||||
| } | |||||
| std::string LightInfo::getDescription() { | |||||
| return description; | |||||
| } | |||||
| } // namespace engine | |||||
| } // namespace rack | |||||
| @@ -34,6 +34,10 @@ Module::~Module() { | |||||
| if (outputInfo) | if (outputInfo) | ||||
| delete outputInfo; | delete outputInfo; | ||||
| } | } | ||||
| for (LightInfo* lightInfo : lightInfos) { | |||||
| if (lightInfo) | |||||
| delete lightInfo; | |||||
| } | |||||
| delete internal; | delete internal; | ||||
| } | } | ||||
| @@ -59,6 +63,8 @@ void Module::config(int numParams, int numInputs, int numOutputs, int numLights) | |||||
| for (int i = 0; i < numOutputs; i++) { | for (int i = 0; i < numOutputs; i++) { | ||||
| configOutput(i); | configOutput(i); | ||||
| } | } | ||||
| // Initialize LightInfos with null | |||||
| lightInfos.resize(numLights); | |||||
| } | } | ||||
| @@ -28,7 +28,7 @@ KnobMode knobMode = KNOB_MODE_LINEAR; | |||||
| float knobLinearSensitivity = 0.001f; | float knobLinearSensitivity = 0.001f; | ||||
| float sampleRate = 44100.0; | float sampleRate = 44100.0; | ||||
| int threadCount = 1; | int threadCount = 1; | ||||
| bool paramTooltip = true; | |||||
| bool tooltips = true; | |||||
| bool cpuMeter = false; | bool cpuMeter = false; | ||||
| bool lockModules = false; | bool lockModules = false; | ||||
| #if defined ARCH_MAC | #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, "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)); | json_object_set_new(rootJ, "cpuMeter", json_boolean(cpuMeter)); | ||||
| @@ -180,9 +180,9 @@ void fromJson(json_t* rootJ) { | |||||
| if (threadCountJ) | if (threadCountJ) | ||||
| threadCount = json_integer_value(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"); | json_t* cpuMeterJ = json_object_get(rootJ, "cpuMeter"); | ||||
| if (cpuMeterJ) | if (cpuMeterJ) | ||||