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