| @@ -16,18 +16,16 @@ struct Param { | |||
| /** Set to 0 for linear, nonzero for exponential */ | |||
| float displayBase = 0.f; | |||
| float displayMultiplier = 1.f; | |||
| int displayPrecision = 2; | |||
| std::string label; | |||
| std::string unit; | |||
| void setup(float minValue, float maxValue, float defaultValue, std::string label = "", std::string unit = "", int displayPrecision = 2, float displayBase = 0.f, float displayMultiplier = 1.f) { | |||
| void setup(float minValue, float maxValue, float defaultValue, std::string label = "", std::string unit = "", float displayBase = 0.f, float displayMultiplier = 1.f) { | |||
| this->value = defaultValue; | |||
| this->minValue = minValue; | |||
| this->maxValue = maxValue; | |||
| this->defaultValue = defaultValue; | |||
| this->label = label; | |||
| this->unit = unit; | |||
| this->displayPrecision = displayPrecision; | |||
| this->displayBase = displayBase; | |||
| this->displayMultiplier = displayMultiplier; | |||
| } | |||
| @@ -8,33 +8,9 @@ namespace rack { | |||
| /** Deletes itself from parent when clicked */ | |||
| struct MenuOverlay : OpaqueWidget { | |||
| void step() override { | |||
| // Adopt parent's size | |||
| box.size = parent->box.size; | |||
| // Fit all children in the box | |||
| for (Widget *child : children) { | |||
| child->box = child->box.nudge(box.zeroPos()); | |||
| } | |||
| Widget::step(); | |||
| } | |||
| void onButton(const event::Button &e) override { | |||
| OpaqueWidget::onButton(e); | |||
| if (e.getConsumed() == this && e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { | |||
| requestedDelete = true; | |||
| } | |||
| } | |||
| void onHoverKey(const event::HoverKey &e) override { | |||
| OpaqueWidget::onHoverKey(e); | |||
| if (e.getConsumed() == this && e.action == GLFW_PRESS && e.key == GLFW_KEY_ESCAPE) { | |||
| requestedDelete = true; | |||
| } | |||
| } | |||
| void step() override; | |||
| void onButton(const event::Button &e) override; | |||
| void onHoverKey(const event::HoverKey &e) override; | |||
| }; | |||
| @@ -50,6 +50,13 @@ struct Quantity { | |||
| return string::f("%.*f", getDisplayPrecision(), getDisplayValue()); | |||
| } | |||
| virtual void setDisplayValueString(std::string s) { | |||
| float displayValue = 0.f; | |||
| int n = std::sscanf(s.c_str(), "%f", &displayValue); | |||
| if (n == 1) | |||
| setDisplayValue(displayValue); | |||
| } | |||
| /** The name of the quantity */ | |||
| virtual std::string getLabel() {return "";} | |||
| @@ -32,6 +32,7 @@ struct TextField : OpaqueWidget { | |||
| /** Replaces the entire text */ | |||
| void setText(std::string text); | |||
| void selectAll(); | |||
| virtual int getTextPosition(math::Vec mousePos); | |||
| }; | |||
| @@ -51,7 +51,7 @@ float ParamQuantity::getDisplayValue() { | |||
| return Quantity::getDisplayValue(); | |||
| if (getParam()->displayBase == 0.f) { | |||
| // Linear | |||
| return getParam()->value * getParam()->displayMultiplier; | |||
| return getValue() * getParam()->displayMultiplier; | |||
| } | |||
| else if (getParam()->displayBase == 1.f) { | |||
| // Fixed (special case of exponential) | |||
| @@ -59,7 +59,7 @@ float ParamQuantity::getDisplayValue() { | |||
| } | |||
| else { | |||
| // Exponential | |||
| return std::pow(getParam()->displayBase, getParam()->value) * getParam()->displayMultiplier; | |||
| return std::pow(getParam()->displayBase, getValue()) * getParam()->displayMultiplier; | |||
| } | |||
| } | |||
| @@ -68,22 +68,26 @@ void ParamQuantity::setDisplayValue(float displayValue) { | |||
| return; | |||
| if (getParam()->displayBase == 0.f) { | |||
| // Linear | |||
| getParam()->value = displayValue / getParam()->displayMultiplier; | |||
| setValue(displayValue / getParam()->displayMultiplier); | |||
| } | |||
| else if (getParam()->displayBase == 1.f) { | |||
| // Fixed | |||
| getParam()->value = getParam()->displayMultiplier; | |||
| setValue(getParam()->displayMultiplier); | |||
| } | |||
| else { | |||
| // Exponential | |||
| getParam()->value = std::log(displayValue / getParam()->displayMultiplier) / std::log(getParam()->displayBase); | |||
| setValue(std::log(displayValue / getParam()->displayMultiplier) / std::log(getParam()->displayBase)); | |||
| } | |||
| } | |||
| int ParamQuantity::getDisplayPrecision() { | |||
| if (!module) | |||
| return Quantity::getDisplayPrecision(); | |||
| return getParam()->displayPrecision; | |||
| float displayValue = getDisplayValue(); | |||
| if (displayValue == 0.f) | |||
| return 0; | |||
| float log = std::log10(std::abs(getDisplayValue())); | |||
| return (int) std::ceil(math::clamp(-log + 3.f, 0.f, 6.f)); | |||
| } | |||
| std::string ParamQuantity::getLabel() { | |||
| @@ -1,4 +1,6 @@ | |||
| #include "app/ParamWidget.hpp" | |||
| #include "ui/MenuOverlay.hpp" | |||
| #include "ui/TextField.hpp" | |||
| #include "app/Scene.hpp" | |||
| #include "context.hpp" | |||
| #include "settings.hpp" | |||
| @@ -8,6 +10,43 @@ | |||
| namespace rack { | |||
| struct ParamField : TextField { | |||
| ParamWidget *paramWidget; | |||
| void step() override { | |||
| // Keep selected | |||
| context()->event->setSelected(this); | |||
| } | |||
| void setParamWidget(ParamWidget *paramWidget) { | |||
| this->paramWidget = paramWidget; | |||
| if (paramWidget->quantity) | |||
| text = paramWidget->quantity->getDisplayValueString(); | |||
| selectAll(); | |||
| } | |||
| void onSelectKey(const event::SelectKey &e) override { | |||
| if (e.action == GLFW_PRESS && (e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER)) { | |||
| if (paramWidget->quantity) | |||
| paramWidget->quantity->setDisplayValueString(text); | |||
| MenuOverlay *overlay = getAncestorOfType<MenuOverlay>(); | |||
| overlay->requestedDelete = true; | |||
| e.consume(this); | |||
| } | |||
| if (e.action == GLFW_PRESS && e.key == GLFW_KEY_ESCAPE) { | |||
| MenuOverlay *overlay = getAncestorOfType<MenuOverlay>(); | |||
| overlay->requestedDelete = true; | |||
| e.consume(this); | |||
| } | |||
| if (!e.getConsumed()) | |||
| TextField::onSelectKey(e); | |||
| } | |||
| }; | |||
| ParamWidget::~ParamWidget() { | |||
| if (quantity) | |||
| delete quantity; | |||
| @@ -27,7 +66,7 @@ void ParamWidget::step() { | |||
| if (tooltip) { | |||
| if (quantity) | |||
| tooltip->text = quantity->getString(); | |||
| tooltip->box.pos = getAbsoluteOffset(box.size); | |||
| tooltip->box.pos = getAbsoluteOffset(box.size).round(); | |||
| } | |||
| OpaqueWidget::step(); | |||
| @@ -50,6 +89,19 @@ void ParamWidget::onButton(const event::Button &e) { | |||
| // dynamic_cast<ParamQuantity*>(quantity)->getParam()->reset(); | |||
| } | |||
| // Shift-click to open value entry | |||
| if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & GLFW_MOD_SHIFT) && !(e.mods & GLFW_MOD_CONTROL)) { | |||
| // Create ParamField | |||
| MenuOverlay *overlay = new MenuOverlay; | |||
| context()->scene->addChild(overlay); | |||
| ParamField *paramField = new ParamField; | |||
| paramField->box.size.x = 100; | |||
| paramField->box.pos = getAbsoluteOffset(box.size).round(); | |||
| paramField->setParamWidget(this); | |||
| overlay->addChild(paramField); | |||
| } | |||
| OpaqueWidget::onButton(e); | |||
| } | |||
| @@ -155,8 +155,7 @@ void TextField::onSelectKey(const event::SelectKey &e) { | |||
| } break; | |||
| case GLFW_KEY_A: { | |||
| if (context()->window->isModPressed()) { | |||
| selection = 0; | |||
| cursor = text.size(); | |||
| selectAll(); | |||
| } | |||
| } break; | |||
| case GLFW_KEY_ENTER: { | |||
| @@ -198,6 +197,11 @@ void TextField::setText(std::string text) { | |||
| onChange(eChange); | |||
| } | |||
| void TextField::selectAll() { | |||
| cursor = text.size(); | |||
| selection = 0; | |||
| } | |||
| int TextField::getTextPosition(math::Vec mousePos) { | |||
| return bndTextFieldTextPosition(context()->window->vg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str(), mousePos.x, mousePos.y); | |||
| } | |||