widgets.cpp (16202B)
1 2 #include "widgets.hpp" 3 #include "skins.hpp" 4 #include "dsp/signal.hpp" 5 6 using namespace bogaudio; 7 using namespace bogaudio::dsp; 8 9 DisplayWidget::DisplayWidget(Module* module) : _module(module) { 10 } 11 12 bool DisplayWidget::isLit() { 13 return _module && !_module->isBypassed(); 14 } 15 16 bool DisplayWidget::isScreenshot() { 17 return !_module; 18 } 19 20 void DisplayWidget::draw(const DrawArgs& args) { 21 if (!isLit()) { 22 drawOnce(args, isScreenshot(), false); 23 } 24 } 25 26 void DisplayWidget::drawLit(const DrawArgs& args) { 27 if (isLit()) { 28 drawOnce(args, false, true); 29 } 30 } 31 32 33 std::string SkinnableWidget::skinSVG(const std::string& base, const std::string& skin) { 34 std::string s = skin; 35 if (s == "default") { 36 s = Skins::skins().defaultKey(); 37 } 38 std::string svg = "res/" + base; 39 if (s != "light") { 40 svg += "-"; 41 svg += s; 42 } 43 svg += ".svg"; 44 return svg; 45 } 46 47 48 Screw::Screw() { 49 skinChanged("default"); 50 } 51 52 void Screw::skinChanged(const std::string& skin) { 53 const char* svg = "res/ComponentLibrary/ScrewSilver.svg"; 54 const char* backgroundFill = Skins::skins().skinCssValue(skin, "background-fill"); 55 if (backgroundFill) { 56 NVGcolor c = Skins::cssColorToNVGColor(backgroundFill, nvgRGBA(0xdd, 0xdd, 0xdd, 0xff)); 57 if ((c.r + c.g + c.b) / 3.0f < 0.5f) { 58 svg = "res/ComponentLibrary/ScrewBlack.svg"; 59 } 60 } 61 setSvg(APP->window->loadSvg(asset::system(svg))); 62 fb->dirty = true; 63 } 64 65 66 BGKnob::BGKnob(const char* svgBase, int dim) { 67 _svgBase = svgBase; 68 setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, skinSVG(_svgBase.c_str()).c_str()))); 69 box.size = Vec(dim, dim); 70 shadow->blurRadius = 2.0; 71 // k->shadow->opacity = 0.15; 72 shadow->box.pos = Vec(0.0, 3.0); 73 } 74 75 void BGKnob::redraw() { 76 event::Change c; 77 onChange(c); 78 } 79 80 void BGKnob::skinChanged(const std::string& skin) { 81 setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, skinSVG(_svgBase.c_str(), skin).c_str()))); 82 fb->dirty = true; 83 } 84 85 86 Knob16::Knob16() : BGKnob("knob_16px", 16) { 87 shadow->box.pos = Vec(0.0, 2.5); 88 } 89 90 91 Knob19::Knob19() : BGKnob("knob_19px", 19) { 92 shadow->box.pos = Vec(0.0, 2.5); 93 } 94 95 96 Knob26::Knob26() : BGKnob("knob_26px", 26) { 97 } 98 99 100 Knob29::Knob29() : BGKnob("knob_29px", 29) { 101 } 102 103 104 Knob38::Knob38() : BGKnob("knob_38px", 38) { 105 } 106 107 108 Knob45::Knob45() : BGKnob("knob_45px", 45) { 109 } 110 111 112 Knob68::Knob68() : BGKnob("knob_68px", 68) { 113 shadow->box.pos = Vec(0.0, 4.0); 114 } 115 116 117 void IndicatorKnob::IKWidget::setAngle(float a) { 118 assert(a >= -1.0f && a <= 1.0f); 119 120 const float range = 0.83f * M_PI; 121 _angle = a * range; 122 if (_unipolarCB && _unipolarCB()) { 123 _angle = 2.0f * _angle - range; 124 } 125 if (a < 0.0f) { 126 _color.r = 1.0f; // 0xff 127 _color.g = 0.6f; // 0x99 128 _color.b = 0.0f; // 0x00 129 _color.a = -a; 130 } 131 else { 132 _color.r = 0.333f; // 0x55 133 _color.g = 1.0f; // 0xff 134 _color.b = 0.333f; // 0x55 135 _color.a = a; 136 } 137 } 138 139 void IndicatorKnob::IKWidget::draw(const DrawArgs& args) { 140 nvgSave(args.vg); 141 142 float c = box.size.x * 0.5f; 143 nvgTranslate(args.vg, c, c); 144 nvgRotate(args.vg, _angle); 145 nvgTranslate(args.vg, -c, -c); 146 147 float r = c - 0.2f; // FIXME: need to scale everything if there is ever a dim != 19. 148 nvgBeginPath(args.vg); 149 nvgCircle(args.vg, c, c, r); 150 nvgFillColor(args.vg, _rim); 151 nvgFill(args.vg); 152 153 r -= 2.0f; 154 nvgBeginPath(args.vg); 155 nvgCircle(args.vg, c, c, r); 156 nvgFillColor(args.vg, _center); 157 nvgFill(args.vg); 158 if (!_drawColorsCB || _drawColorsCB()) { 159 nvgBeginPath(args.vg); 160 nvgCircle(args.vg, c, c, r); 161 nvgFillColor(args.vg, _color); 162 nvgFill(args.vg); 163 164 r -= 0.15f; 165 nvgBeginPath(args.vg); 166 nvgCircle(args.vg, c, c, r); 167 nvgStrokeColor(args.vg, nvgRGBA(0x66, 0x66, 0x66, 0x7f)); 168 nvgStrokeWidth(args.vg, 0.3f); 169 nvgStroke(args.vg); 170 171 r -= 0.3f; 172 nvgBeginPath(args.vg); 173 nvgCircle(args.vg, c, c, r); 174 nvgStrokeColor(args.vg, nvgRGBA(0x77, 0x77, 0x77, 0x7f)); 175 nvgStrokeWidth(args.vg, 0.3f); 176 nvgStroke(args.vg); 177 178 r -= 0.3f; 179 nvgBeginPath(args.vg); 180 nvgCircle(args.vg, c, c, r); 181 nvgStrokeColor(args.vg, nvgRGBA(0x88, 0x88, 0x88, 0x7f)); 182 nvgStrokeWidth(args.vg, 0.3f); 183 nvgStroke(args.vg); 184 185 r -= 0.3f; 186 nvgBeginPath(args.vg); 187 nvgCircle(args.vg, c, c, r); 188 nvgStrokeColor(args.vg, nvgRGBA(0x99, 0x99, 0x99, 0x7f)); 189 nvgStrokeWidth(args.vg, 0.3f); 190 nvgStroke(args.vg); 191 192 r -= 0.15f; 193 nvgBeginPath(args.vg); 194 nvgCircle(args.vg, c, c, r); 195 nvgFillColor(args.vg, nvgRGBA(0xaa, 0xaa, 0xaa, 0x7f)); 196 nvgFill(args.vg); 197 } 198 199 nvgBeginPath(args.vg); 200 nvgCircle(args.vg, c, 1.6f, 1.3f); 201 nvgFillColor(args.vg, nvgRGBA(0xff, 0xff, 0xff, 0xff)); 202 nvgFill(args.vg); 203 204 nvgBeginPath(args.vg); 205 nvgCircle(args.vg, c, 1.9f, 1.3f); 206 nvgFillColor(args.vg, nvgRGBA(0xff, 0xff, 0xff, 0xff)); 207 nvgFill(args.vg); 208 209 nvgRestore(args.vg); 210 } 211 212 IndicatorKnob::IndicatorKnob(int dim) { 213 box.size = math::Vec(dim, dim); 214 box.pos = math::Vec(0, 0); 215 fb = new widget::FramebufferWidget; 216 addChild(fb); 217 fb->box.size = box.size; 218 219 shadow = new CircularShadow; 220 shadow->box.size = box.size; 221 shadow->blurRadius = 2.0; 222 // shadow->opacity = 0.15; 223 shadow->box.pos = Vec(0.0, 3.0); 224 fb->addChild(shadow); 225 226 w = new IKWidget; 227 w->box.size = box.size; 228 w->box.pos = math::Vec(0, 0); 229 fb->addChild(w); 230 231 skinChanged("default"); 232 } 233 234 void IndicatorKnob::onHover(const event::Hover& e) { 235 math::Vec c = box.size.div(2); 236 float dist = e.pos.minus(c).norm(); 237 if (dist <= c.x) { 238 Knob::onHover(e); 239 } 240 } 241 242 void IndicatorKnob::onLeave(const LeaveEvent& e) { 243 Knob::onLeave(e); 244 redraw(); 245 } 246 247 void IndicatorKnob::onChange(const event::Change& e) { 248 fb->dirty = true; 249 if (getParamQuantity()) { 250 w->setAngle(getParamQuantity()->getValue()); 251 } 252 Knob::onChange(e); 253 } 254 255 void IndicatorKnob::redraw() { 256 event::Change c; 257 onChange(c); 258 } 259 260 bool IndicatorKnob::isLit() { 261 return module && !module->isBypassed() && getParamQuantity() && 262 (getParamQuantity()->getValue() < -0.01f || getParamQuantity()->getValue() > 0.01f) && 263 (!w->_drawColorsCB || w->_drawColorsCB()); 264 } 265 266 void IndicatorKnob::draw(const DrawArgs& args) { 267 if (!isLit()) { 268 Knob::draw(args); 269 } 270 } 271 272 void IndicatorKnob::drawLit(const DrawArgs& args) { 273 Knob::draw(args); 274 } 275 276 void IndicatorKnob::skinChanged(const std::string& skin) { 277 const Skins& skins = Skins::skins(); 278 const char* knobRim = skins.skinCssValue(skin, "knob-rim"); 279 if (knobRim) { 280 w->_rim = Skins::cssColorToNVGColor(knobRim, w->_rim); 281 } 282 const char* knobCenter = skins.skinCssValue(skin, "knob-center"); 283 if (knobCenter) { 284 w->_center = Skins::cssColorToNVGColor(knobCenter, w->_center); 285 } 286 fb->dirty = true; 287 } 288 289 290 Port24::Port24() { 291 setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, skinSVG("port").c_str()))); 292 box.size = Vec(24, 24); 293 shadow->blurRadius = 1.0; 294 shadow->box.pos = Vec(0.0, 1.5); 295 } 296 297 void Port24::skinChanged(const std::string& skin) { 298 setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, skinSVG("port", skin).c_str()))); 299 fb->dirty = true; 300 } 301 302 303 BlankPort24::BlankPort24() { 304 setSvg(NULL); 305 box.size = Vec(24, 24); 306 } 307 308 309 SliderSwitch::SliderSwitch() { 310 shadow = new CircularShadow(); 311 addChild(shadow); 312 shadow->box.size = Vec(); 313 } 314 315 316 SliderSwitch2State14::SliderSwitch2State14() { 317 addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/slider_switch_2_14px_0.svg"))); 318 addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/slider_switch_2_14px_1.svg"))); 319 shadow->box.size = Vec(14.0, 24.0); 320 shadow->blurRadius = 1.0; 321 shadow->box.pos = Vec(0.0, 7.0); 322 } 323 324 325 Button18::Button18() { 326 addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/button_18px_0.svg"))); 327 addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/button_18px_1.svg"))); 328 box.size = Vec(18, 18); 329 momentary = true; 330 } 331 332 333 StatefulButton::StatefulButton(const char* offSvgPath, const char* onSvgPath) { 334 shadow = new CircularShadow(); 335 addChild(shadow); 336 337 _svgWidget = new SvgWidget(); 338 addChild(_svgWidget); 339 340 auto svg = APP->window->loadSvg(asset::plugin(pluginInstance, offSvgPath)); 341 _frames.push_back(svg); 342 _frames.push_back(APP->window->loadSvg(asset::plugin(pluginInstance, onSvgPath))); 343 344 _svgWidget->setSvg(svg); 345 box.size = _svgWidget->box.size; 346 shadow->box.size = _svgWidget->box.size; 347 shadow->blurRadius = 1.0; 348 shadow->box.pos = Vec(0.0, 1.0); 349 } 350 351 void StatefulButton::onDragStart(const event::DragStart& e) { 352 if (getParamQuantity()) { 353 _svgWidget->setSvg(_frames[1]); 354 if (getParamQuantity()->getValue() >= getParamQuantity()->getMaxValue()) { 355 getParamQuantity()->setValue(getParamQuantity()->getMinValue()); 356 } 357 else { 358 getParamQuantity()->setValue(getParamQuantity()->getValue() + 1.0); 359 } 360 } 361 } 362 363 void StatefulButton::onDragEnd(const event::DragEnd& e) { 364 _svgWidget->setSvg(_frames[0]); 365 } 366 367 368 StatefulButton9::StatefulButton9() : StatefulButton("res/button_9px_0.svg", "res/button_9px_1.svg") { 369 } 370 371 372 StatefulButton18::StatefulButton18() : StatefulButton("res/button_18px_0.svg", "res/button_18px_1.svg") { 373 } 374 375 376 ToggleButton18::ToggleButton18() { 377 addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/button_18px_0.svg"))); 378 addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/button_18px_1.svg"))); 379 } 380 381 382 IndicatorButtonGreen9::IndicatorButtonGreen9() { 383 addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/button_9px_0.svg"))); 384 addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/button_9px_1_green.svg"))); 385 } 386 387 bool IndicatorButtonGreen9::isLit() { 388 return module && !module->isBypassed() && getParamQuantity() && getParamQuantity()->getValue() > 0.0f; 389 } 390 391 void IndicatorButtonGreen9::draw(const DrawArgs& args) { 392 if (!isLit()) { 393 SvgSwitch::draw(args); 394 } 395 } 396 397 void IndicatorButtonGreen9::drawLit(const DrawArgs& args) { 398 SvgSwitch::draw(args); 399 } 400 401 402 void InvertingIndicatorButton::IIBWidget::setValue(float v) { 403 assert(v >= -1.0f && v <= 1.0f); 404 405 if (v < 0.0f) { 406 _color.r = 1.0f; // 0xff 407 _color.g = 0.6f; // 0x99 408 _color.b = 0.0f; // 0x00 409 _color.a = -v; 410 } 411 else { 412 _color.r = 0.333f; // 0x55 413 _color.g = 1.0f; // 0xff 414 _color.b = 0.333f; // 0x55 415 _color.a = v; 416 } 417 } 418 419 void InvertingIndicatorButton::IIBWidget::draw(const DrawArgs& args) { 420 nvgSave(args.vg); 421 422 float c = box.size.x * 0.5f; 423 float s = ((float)_dim) / 18.0f; 424 float r = c - 0.1f; 425 nvgBeginPath(args.vg); 426 nvgCircle(args.vg, c, c, r); 427 nvgFillColor(args.vg, nvgRGBA(0x88, 0x88, 0x88, 0xff)); 428 nvgFill(args.vg); 429 430 r -= std::max(0.3f, 0.5f * s); 431 nvgBeginPath(args.vg); 432 nvgCircle(args.vg, c, c, r); 433 nvgFillColor(args.vg, nvgRGBA(0x33, 0x33, 0x33, 0xff)); 434 nvgFill(args.vg); 435 436 r -= std::max(0.2f, 0.6f * s); 437 nvgBeginPath(args.vg); 438 nvgCircle(args.vg, c, c, r); 439 nvgFillColor(args.vg, nvgRGBA(0xee, 0xee, 0xee, 0xff)); 440 nvgFill(args.vg); 441 nvgBeginPath(args.vg); 442 nvgCircle(args.vg, c, c, r); 443 nvgFillColor(args.vg, _color); 444 nvgFill(args.vg); 445 nvgCircle(args.vg, c, c, r); 446 nvgFillColor(args.vg, nvgRGBA(0x66, 0x66, 0x66, 0x7f)); 447 nvgFill(args.vg); 448 449 r -= std::max(0.2f, 0.6f * s); 450 nvgBeginPath(args.vg); 451 nvgCircle(args.vg, c, c, r); 452 nvgFillColor(args.vg, nvgRGBA(0xee, 0xee, 0xee, 0xff)); 453 nvgFill(args.vg); 454 nvgBeginPath(args.vg); 455 nvgCircle(args.vg, c, c, r); 456 nvgFillColor(args.vg, _color); 457 nvgFill(args.vg); 458 nvgCircle(args.vg, c, c, r); 459 nvgFillColor(args.vg, nvgRGBA(0x88, 0x88, 0x88, 0x7f)); 460 nvgFill(args.vg); 461 462 r -= std::max(0.2f, 0.6f * s); 463 nvgBeginPath(args.vg); 464 nvgCircle(args.vg, c, c, r); 465 nvgFillColor(args.vg, nvgRGBA(0xee, 0xee, 0xee, 0xff)); 466 nvgFill(args.vg); 467 nvgBeginPath(args.vg); 468 nvgCircle(args.vg, c, c, r); 469 nvgFillColor(args.vg, _color); 470 nvgFill(args.vg); 471 nvgCircle(args.vg, c, c, r); 472 nvgFillColor(args.vg, nvgRGBA(0xaa, 0xaa, 0xaa, 0x7f)); 473 nvgFill(args.vg); 474 475 nvgRestore(args.vg); 476 } 477 478 InvertingIndicatorButton::InvertingIndicatorButton(int dim) { 479 box.size = math::Vec(dim, dim); 480 // box.pos = math::Vec(0, 0); 481 fb = new widget::FramebufferWidget; 482 addChild(fb); 483 fb->box.size = box.size; 484 485 shadow = new CircularShadow; 486 shadow->box.size = box.size; 487 shadow->blurRadius = 2.0; 488 shadow->box.pos = Vec(0.0, 1.0); 489 fb->addChild(shadow); 490 491 w = new IIBWidget(dim); 492 w->box.size = box.size; 493 // w->box.pos = math::Vec(0, 0); 494 fb->addChild(w); 495 } 496 497 void InvertingIndicatorButton::onHover(const event::Hover& e) { 498 math::Vec c = box.size.div(2); 499 float dist = e.pos.minus(c).norm(); 500 if (dist <= c.x) { 501 ParamWidget::onHover(e); 502 } 503 } 504 505 void InvertingIndicatorButton::onButton(const event::Button& e) { 506 ParamWidget::onButton(e); 507 if (!getParamQuantity() || !(e.action == GLFW_PRESS && (e.mods & RACK_MOD_MASK) == 0) || e.button == GLFW_MOUSE_BUTTON_RIGHT) { 508 return; 509 } 510 511 float value = getParamQuantity()->getValue(); 512 if (value <= -1.0f) { 513 getParamQuantity()->setValue(0.0f); 514 } 515 else if (value < 1.0f) { 516 getParamQuantity()->setValue(1.0f); 517 } 518 else if (getParamQuantity()->minValue < 0.0f && (!clickToInvertCB || clickToInvertCB())) { 519 getParamQuantity()->setValue(-1.0f); 520 } 521 else { 522 getParamQuantity()->setValue(0.0f); 523 } 524 } 525 526 void InvertingIndicatorButton::onChange(const event::Change& e) { 527 fb->dirty = true; 528 if (getParamQuantity()) { 529 float v = getParamQuantity()->getValue(); 530 w->setValue(v); 531 if (onChangeCB) { 532 onChangeCB(getParamQuantity()->paramId, v); 533 } 534 } 535 ParamWidget::onChange(e); 536 } 537 538 bool InvertingIndicatorButton::isLit() { 539 return module && !module->isBypassed() && getParamQuantity() && 540 (getParamQuantity()->getValue() < -0.01f || getParamQuantity()->getValue() > 0.01f); 541 } 542 543 void InvertingIndicatorButton::draw(const DrawArgs& args) { 544 if (!isLit()) { 545 ParamWidget::draw(args); 546 } 547 } 548 549 void InvertingIndicatorButton::drawLit(const DrawArgs& args) { 550 ParamWidget::draw(args); 551 } 552 553 554 NVGcolor bogaudio::decibelsToColor(float db) { 555 if (db < -80.0f) { 556 return nvgRGBA(0x00, 0x00, 0x00, 0x00); 557 } 558 if (db < -24.0f) { 559 return nvgRGBA(0x55, 0xff, 0x00, (1.0f - (db + 24.0f) / -56.0f) * (float)0xff); 560 } 561 if (db < 0.0f) { 562 return nvgRGBA((1.0f - db / -24.0f) * 0xff, 0xff, 0x00, 0xff); 563 } 564 return nvgRGBA(0xff, (1.0f - std::min(db, 9.0f) / 9.0f) * 0xff, 0x00, 0xff); 565 } 566 567 568 bool VUSlider::isLit() { 569 float db = _vuLevel ? *_vuLevel : 0.0f; 570 bool stereo = false; 571 float stereoDb = 0.0f; 572 if (_stereoVuLevel) { 573 stereo = true; 574 stereoDb = *_stereoVuLevel; 575 } 576 return module && !module->isBypassed() && (db > 0.0f || (stereo && stereoDb > 0.0f)); 577 } 578 579 void VUSlider::draw(const DrawArgs& args) { 580 581 nvgSave(args.vg); 582 { 583 nvgBeginPath(args.vg); 584 nvgRoundedRect(args.vg, 6, 3, 6, box.size.y - 6, 2); 585 nvgFillColor(args.vg, nvgRGBA(0x22, 0x22, 0x22, 0xff)); 586 nvgFill(args.vg); 587 nvgStrokeColor(args.vg, nvgRGBA(0x88, 0x88, 0x88, 0xff)); 588 nvgStroke(args.vg); 589 } 590 nvgRestore(args.vg); 591 592 nvgSave(args.vg); 593 { 594 drawTranslate(args); 595 nvgBeginPath(args.vg); 596 nvgRoundedRect(args.vg, 0, 0, 18, 13, 1.5); 597 nvgFillColor(args.vg, nvgRGBA(0x77, 0x77, 0x77, 0xff)); 598 nvgFill(args.vg); 599 600 nvgBeginPath(args.vg); 601 nvgRect(args.vg, 0, 2, 18, 9); 602 nvgFillColor(args.vg, nvgRGBA(0x44, 0x44, 0x44, 0xff)); 603 nvgFill(args.vg); 604 605 nvgBeginPath(args.vg); 606 nvgRect(args.vg, 0, 6, 18, 1); 607 nvgFillColor(args.vg, nvgRGBA(0xfa, 0xfa, 0xfa, 0xff)); 608 nvgFill(args.vg); 609 610 nvgBeginPath(args.vg); 611 nvgRoundedRect(args.vg, 2, 4, 14, 5, 1.0); 612 nvgFillColor(args.vg, nvgRGBA(0xaa, 0xaa, 0xaa, 0xff)); 613 nvgFill(args.vg); 614 } 615 nvgRestore(args.vg); 616 } 617 618 void VUSlider::drawLit(const DrawArgs& args) { 619 float db = _vuLevel ? *_vuLevel : 0.0f; 620 bool stereo = false; 621 float stereoDb = 0.0f; 622 if (_stereoVuLevel) { 623 stereo = true; 624 stereoDb = *_stereoVuLevel; 625 } 626 627 nvgSave(args.vg); 628 drawTranslate(args); 629 if (db > 0.0f) { 630 nvgSave(args.vg); 631 nvgBeginPath(args.vg); 632 if (stereo) { 633 nvgRoundedRect(args.vg, 2, 4, stereo ? 7 : 14, 5, 1.0); 634 } 635 else { 636 nvgRoundedRect(args.vg, 2, 4, 14, 5, 1.0); 637 } 638 nvgFillColor(args.vg, decibelsToColor(amplitudeToDecibels(db))); 639 nvgFill(args.vg); 640 nvgRestore(args.vg); 641 } 642 if (stereo && stereoDb > 0.0f) { 643 nvgSave(args.vg); 644 nvgBeginPath(args.vg); 645 nvgRoundedRect(args.vg, 9, 4, 7, 5, 1.0); 646 nvgFillColor(args.vg, decibelsToColor(amplitudeToDecibels(stereoDb))); 647 nvgFill(args.vg); 648 nvgRestore(args.vg); 649 } 650 nvgRestore(args.vg); 651 } 652 653 void VUSlider::drawTranslate(const DrawArgs& args) { 654 float level = 0.0f; 655 if (getParamQuantity()) { 656 level = getParamQuantity()->getValue(); 657 } 658 else { 659 float minDb = -60.0f; 660 float maxDb = 6.0f; 661 level = fabsf(minDb) / (maxDb - minDb); 662 } 663 nvgTranslate(args.vg, 0, (box.size.y - 13.0f) * (1.0f - level)); 664 }