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