| @@ -1,6 +1,7 @@ | |||
| #pragma once | |||
| #include <app/common.hpp> | |||
| #include <app/MultiLightWidget.hpp> | |||
| #include <ui/Tooltip.hpp> | |||
| #include <engine/Module.hpp> | |||
| @@ -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; | |||
| }; | |||
| @@ -12,6 +12,7 @@ struct MultiLightWidget : LightWidget { | |||
| /** Colors of each value state */ | |||
| std::vector<NVGcolor> 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<float>& brightnesses); | |||
| @@ -18,7 +18,6 @@ struct PortWidget : widget::OpaqueWidget { | |||
| int portId; | |||
| ui::Tooltip* tooltip = NULL; | |||
| bool hovered = false; | |||
| 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/ParamQuantity.hpp> | |||
| #include <engine/PortInfo.hpp> | |||
| #include <engine/LightInfo.hpp> | |||
| namespace rack { | |||
| @@ -43,9 +44,15 @@ struct Module { | |||
| std::vector<Input> inputs; | |||
| std::vector<Output> outputs; | |||
| 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<PortInfo*> inputInfos; | |||
| std::vector<PortInfo*> outputInfos; | |||
| std::vector<LightInfo*> 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 <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. | |||
| @@ -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; | |||
| @@ -5,6 +5,7 @@ | |||
| #include <context.hpp> | |||
| #include <patch.hpp> | |||
| #include <settings.hpp> | |||
| #include <event.hpp> | |||
| #include <engine/Engine.hpp> | |||
| #include <engine/Port.hpp> | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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"; | |||
| @@ -1,10 +1,96 @@ | |||
| #include <app/ModuleLightWidget.hpp> | |||
| #include <app/Scene.hpp> | |||
| #include <context.hpp> | |||
| #include <settings.hpp> | |||
| 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<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 rack | |||
| @@ -6,6 +6,11 @@ namespace rack { | |||
| namespace app { | |||
| int MultiLightWidget::getNumColors() { | |||
| return baseColors.size(); | |||
| } | |||
| void MultiLightWidget::addBaseColor(NVGcolor baseColor) { | |||
| baseColors.push_back(baseColor); | |||
| } | |||
| @@ -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() { | |||
| @@ -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(); | |||
| } | |||
| @@ -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) | |||
| 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); | |||
| } | |||
| @@ -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) | |||