ImageBaseWidgets.cpp (27230B)
1 /* 2 * DISTRHO Plugin Framework (DPF) 3 * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> 4 * 5 * Permission to use, copy, modify, and/or distribute this software for any purpose with 6 * or without fee is hereby granted, provided that the above copyright notice and this 7 * permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD 10 * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN 11 * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 12 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 13 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 14 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17 #include "../ImageBaseWidgets.hpp" 18 #include "../Color.hpp" 19 20 START_NAMESPACE_DGL 21 22 // -------------------------------------------------------------------------------------------------------------------- 23 24 template <class ImageType> 25 ImageBaseAboutWindow<ImageType>::ImageBaseAboutWindow(Window& transientParentWindow, const ImageType& image) 26 : StandaloneWindow(transientParentWindow.getApp(), transientParentWindow), 27 img(image) 28 { 29 setResizable(false); 30 setTitle("About"); 31 32 if (image.isValid()) 33 { 34 setSize(image.getSize()); 35 setGeometryConstraints(image.getWidth(), image.getHeight(), true, true); 36 } 37 38 done(); 39 } 40 41 template <class ImageType> 42 ImageBaseAboutWindow<ImageType>::ImageBaseAboutWindow(TopLevelWidget* const topLevelWidget, const ImageType& image) 43 : StandaloneWindow(topLevelWidget->getApp(), topLevelWidget->getWindow()), 44 img(image) 45 { 46 setResizable(false); 47 setTitle("About"); 48 49 if (image.isValid()) 50 { 51 setSize(image.getSize()); 52 setGeometryConstraints(image.getWidth(), image.getHeight(), true, true); 53 } 54 55 done(); 56 } 57 58 template <class ImageType> 59 void ImageBaseAboutWindow<ImageType>::setImage(const ImageType& image) 60 { 61 if (img == image) 62 return; 63 64 if (image.isInvalid()) 65 { 66 img = image; 67 return; 68 } 69 70 reinit(); 71 72 img = image; 73 74 setSize(image.getSize()); 75 setGeometryConstraints(image.getWidth(), image.getHeight(), true, true); 76 77 done(); 78 } 79 80 template <class ImageType> 81 void ImageBaseAboutWindow<ImageType>::onDisplay() 82 { 83 img.draw(getGraphicsContext()); 84 } 85 86 template <class ImageType> 87 bool ImageBaseAboutWindow<ImageType>::onKeyboard(const KeyboardEvent& ev) 88 { 89 if (ev.press && ev.key == kKeyEscape) 90 { 91 close(); 92 return true; 93 } 94 95 return false; 96 } 97 98 template <class ImageType> 99 bool ImageBaseAboutWindow<ImageType>::onMouse(const MouseEvent& ev) 100 { 101 if (ev.press) 102 { 103 close(); 104 return true; 105 } 106 107 return false; 108 } 109 110 // -------------------------------------------------------------------------------------------------------------------- 111 112 template <class ImageType> 113 struct ImageBaseButton<ImageType>::PrivateData : public ButtonEventHandler::Callback { 114 ImageBaseButton<ImageType>::Callback* callback; 115 ImageType imageNormal; 116 ImageType imageHover; 117 ImageType imageDown; 118 119 PrivateData(const ImageType& normal, const ImageType& hover, const ImageType& down) 120 : callback(nullptr), 121 imageNormal(normal), 122 imageHover(hover), 123 imageDown(down) {} 124 125 void buttonClicked(SubWidget* widget, int button) override 126 { 127 if (callback != nullptr) 128 if (ImageBaseButton* const imageButton = dynamic_cast<ImageBaseButton*>(widget)) 129 callback->imageButtonClicked(imageButton, button); 130 } 131 132 DISTRHO_DECLARE_NON_COPYABLE(PrivateData) 133 }; 134 135 // -------------------------------------------------------------------------------------------------------------------- 136 137 template <class ImageType> 138 ImageBaseButton<ImageType>::ImageBaseButton(Widget* const parentWidget, const ImageType& image) 139 : SubWidget(parentWidget), 140 ButtonEventHandler(this), 141 pData(new PrivateData(image, image, image)) 142 { 143 ButtonEventHandler::setCallback(pData); 144 setSize(image.getSize()); 145 } 146 147 template <class ImageType> 148 ImageBaseButton<ImageType>::ImageBaseButton(Widget* const parentWidget, const ImageType& imageNormal, const ImageType& imageDown) 149 : SubWidget(parentWidget), 150 ButtonEventHandler(this), 151 pData(new PrivateData(imageNormal, imageNormal, imageDown)) 152 { 153 DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize()); 154 155 ButtonEventHandler::setCallback(pData); 156 setSize(imageNormal.getSize()); 157 } 158 159 template <class ImageType> 160 ImageBaseButton<ImageType>::ImageBaseButton(Widget* const parentWidget, const ImageType& imageNormal, const ImageType& imageHover, const ImageType& imageDown) 161 : SubWidget(parentWidget), 162 ButtonEventHandler(this), 163 pData(new PrivateData(imageNormal, imageHover, imageDown)) 164 { 165 DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageHover.getSize() && imageHover.getSize() == imageDown.getSize()); 166 167 ButtonEventHandler::setCallback(pData); 168 setSize(imageNormal.getSize()); 169 } 170 171 template <class ImageType> 172 ImageBaseButton<ImageType>::~ImageBaseButton() 173 { 174 delete pData; 175 } 176 177 template <class ImageType> 178 void ImageBaseButton<ImageType>::setCallback(Callback* callback) noexcept 179 { 180 pData->callback = callback; 181 } 182 183 template <class ImageType> 184 void ImageBaseButton<ImageType>::onDisplay() 185 { 186 const GraphicsContext& context(getGraphicsContext()); 187 188 const State state = ButtonEventHandler::getState(); 189 190 if (ButtonEventHandler::isCheckable()) 191 { 192 if (ButtonEventHandler::isChecked()) 193 pData->imageDown.draw(context); 194 else if (state & kButtonStateHover) 195 pData->imageHover.draw(context); 196 else 197 pData->imageNormal.draw(context); 198 } 199 else 200 { 201 if (state & kButtonStateActive) 202 pData->imageDown.draw(context); 203 else if (state & kButtonStateHover) 204 pData->imageHover.draw(context); 205 else 206 pData->imageNormal.draw(context); 207 } 208 } 209 210 template <class ImageType> 211 bool ImageBaseButton<ImageType>::onMouse(const MouseEvent& ev) 212 { 213 if (SubWidget::onMouse(ev)) 214 return true; 215 return ButtonEventHandler::mouseEvent(ev); 216 } 217 218 template <class ImageType> 219 bool ImageBaseButton<ImageType>::onMotion(const MotionEvent& ev) 220 { 221 if (SubWidget::onMotion(ev)) 222 return true; 223 return ButtonEventHandler::motionEvent(ev); 224 } 225 226 // -------------------------------------------------------------------------------------------------------------------- 227 228 template <class ImageType> 229 struct ImageBaseKnob<ImageType>::PrivateData : public KnobEventHandler::Callback { 230 ImageBaseKnob<ImageType>::Callback* callback; 231 ImageType image; 232 233 int rotationAngle; 234 235 bool alwaysRepaint; 236 bool isImgVertical; 237 uint imgLayerWidth; 238 uint imgLayerHeight; 239 uint imgLayerCount; 240 bool isReady; 241 242 union { 243 uint glTextureId; 244 void* cairoSurface; 245 }; 246 247 explicit PrivateData(const ImageType& img) 248 : callback(nullptr), 249 image(img), 250 rotationAngle(0), 251 alwaysRepaint(false), 252 isImgVertical(img.getHeight() > img.getWidth()), 253 imgLayerWidth(isImgVertical ? img.getWidth() : img.getHeight()), 254 imgLayerHeight(imgLayerWidth), 255 imgLayerCount(isImgVertical ? img.getHeight()/imgLayerHeight : img.getWidth()/imgLayerWidth), 256 isReady(false) 257 { 258 init(); 259 } 260 261 explicit PrivateData(PrivateData* const other) 262 : callback(other->callback), 263 image(other->image), 264 rotationAngle(other->rotationAngle), 265 alwaysRepaint(other->alwaysRepaint), 266 isImgVertical(other->isImgVertical), 267 imgLayerWidth(other->imgLayerWidth), 268 imgLayerHeight(other->imgLayerHeight), 269 imgLayerCount(other->imgLayerCount), 270 isReady(false) 271 { 272 init(); 273 } 274 275 void assignFrom(PrivateData* const other) 276 { 277 cleanup(); 278 image = other->image; 279 rotationAngle = other->rotationAngle; 280 callback = other->callback; 281 alwaysRepaint = other->alwaysRepaint; 282 isImgVertical = other->isImgVertical; 283 imgLayerWidth = other->imgLayerWidth; 284 imgLayerHeight = other->imgLayerHeight; 285 imgLayerCount = other->imgLayerCount; 286 isReady = false; 287 init(); 288 } 289 290 ~PrivateData() 291 { 292 cleanup(); 293 } 294 295 void knobDragStarted(SubWidget* const widget) override 296 { 297 if (callback != nullptr) 298 if (ImageBaseKnob* const imageKnob = dynamic_cast<ImageBaseKnob*>(widget)) 299 callback->imageKnobDragStarted(imageKnob); 300 } 301 302 void knobDragFinished(SubWidget* const widget) override 303 { 304 if (callback != nullptr) 305 if (ImageBaseKnob* const imageKnob = dynamic_cast<ImageBaseKnob*>(widget)) 306 callback->imageKnobDragFinished(imageKnob); 307 } 308 309 void knobValueChanged(SubWidget* const widget, const float value) override 310 { 311 if (rotationAngle == 0 || alwaysRepaint) 312 isReady = false; 313 314 if (callback != nullptr) 315 if (ImageBaseKnob* const imageKnob = dynamic_cast<ImageBaseKnob*>(widget)) 316 callback->imageKnobValueChanged(imageKnob, value); 317 } 318 319 // implemented independently per graphics backend 320 void init(); 321 void cleanup(); 322 323 DISTRHO_DECLARE_NON_COPYABLE(PrivateData) 324 }; 325 326 // -------------------------------------------------------------------------------------------------------------------- 327 328 template <class ImageType> 329 ImageBaseKnob<ImageType>::ImageBaseKnob(Widget* const parentWidget, 330 const ImageType& image, 331 const Orientation orientation) noexcept 332 : SubWidget(parentWidget), 333 KnobEventHandler(this), 334 pData(new PrivateData(image)) 335 { 336 KnobEventHandler::setCallback(pData); 337 setOrientation(orientation); 338 setSize(pData->imgLayerWidth, pData->imgLayerHeight); 339 } 340 341 template <class ImageType> 342 ImageBaseKnob<ImageType>::ImageBaseKnob(const ImageBaseKnob<ImageType>& imageKnob) 343 : SubWidget(imageKnob.getParentWidget()), 344 KnobEventHandler(this, imageKnob), 345 pData(new PrivateData(imageKnob.pData)) 346 { 347 KnobEventHandler::setCallback(pData); 348 setOrientation(imageKnob.getOrientation()); 349 setSize(pData->imgLayerWidth, pData->imgLayerHeight); 350 } 351 352 template <class ImageType> 353 ImageBaseKnob<ImageType>& ImageBaseKnob<ImageType>::operator=(const ImageBaseKnob<ImageType>& imageKnob) 354 { 355 KnobEventHandler::operator=(imageKnob); 356 pData->assignFrom(imageKnob.pData); 357 setSize(pData->imgLayerWidth, pData->imgLayerHeight); 358 return *this; 359 } 360 361 template <class ImageType> 362 ImageBaseKnob<ImageType>::~ImageBaseKnob() 363 { 364 delete pData; 365 } 366 367 template <class ImageType> 368 void ImageBaseKnob<ImageType>::setCallback(Callback* callback) noexcept 369 { 370 pData->callback = callback; 371 } 372 373 template <class ImageType> 374 void ImageBaseKnob<ImageType>::setImageLayerCount(uint count) noexcept 375 { 376 DISTRHO_SAFE_ASSERT_RETURN(count > 1,); 377 378 pData->imgLayerCount = count; 379 380 if (pData->isImgVertical) 381 pData->imgLayerHeight = pData->image.getHeight()/count; 382 else 383 pData->imgLayerWidth = pData->image.getWidth()/count; 384 385 setSize(pData->imgLayerWidth, pData->imgLayerHeight); 386 } 387 388 template <class ImageType> 389 void ImageBaseKnob<ImageType>::setRotationAngle(int angle) 390 { 391 if (pData->rotationAngle == angle) 392 return; 393 394 pData->rotationAngle = angle; 395 pData->isReady = false; 396 } 397 398 template <class ImageType> 399 bool ImageBaseKnob<ImageType>::setValue(float value, bool sendCallback) noexcept 400 { 401 if (KnobEventHandler::setValue(value, sendCallback)) 402 { 403 if (pData->rotationAngle == 0 || pData->alwaysRepaint) 404 pData->isReady = false; 405 406 return true; 407 } 408 409 return false; 410 } 411 412 template <class ImageType> 413 bool ImageBaseKnob<ImageType>::onMouse(const MouseEvent& ev) 414 { 415 if (SubWidget::onMouse(ev)) 416 return true; 417 return KnobEventHandler::mouseEvent(ev, getTopLevelWidget()->getScaleFactor()); 418 } 419 420 template <class ImageType> 421 bool ImageBaseKnob<ImageType>::onMotion(const MotionEvent& ev) 422 { 423 if (SubWidget::onMotion(ev)) 424 return true; 425 return KnobEventHandler::motionEvent(ev, getTopLevelWidget()->getScaleFactor()); 426 } 427 428 template <class ImageType> 429 bool ImageBaseKnob<ImageType>::onScroll(const ScrollEvent& ev) 430 { 431 if (SubWidget::onScroll(ev)) 432 return true; 433 return KnobEventHandler::scrollEvent(ev); 434 } 435 436 // -------------------------------------------------------------------------------------------------------------------- 437 438 template <class ImageType> 439 struct ImageBaseSlider<ImageType>::PrivateData { 440 ImageType image; 441 float minimum; 442 float maximum; 443 float step; 444 float value; 445 float valueDef; 446 float valueTmp; 447 bool usingDefault; 448 449 bool dragging; 450 bool checkable; 451 bool inverted; 452 bool valueIsSet; 453 double startedX; 454 double startedY; 455 456 Callback* callback; 457 458 Point<int> startPos; 459 Point<int> endPos; 460 Rectangle<double> sliderArea; 461 462 PrivateData(const ImageType& img) 463 : image(img), 464 minimum(0.0f), 465 maximum(1.0f), 466 step(0.0f), 467 value(0.5f), 468 valueDef(value), 469 valueTmp(value), 470 usingDefault(false), 471 dragging(false), 472 checkable(false), 473 inverted(false), 474 valueIsSet(false), 475 startedX(0.0), 476 startedY(0.0), 477 callback(nullptr), 478 startPos(), 479 endPos(), 480 sliderArea() {} 481 482 void recheckArea() noexcept 483 { 484 if (startPos.getY() == endPos.getY()) 485 { 486 // horizontal 487 sliderArea = Rectangle<double>(startPos.getX(), 488 startPos.getY(), 489 endPos.getX() + static_cast<int>(image.getWidth()) - startPos.getX(), 490 static_cast<int>(image.getHeight())); 491 } 492 else 493 { 494 // vertical 495 sliderArea = Rectangle<double>(startPos.getX(), 496 startPos.getY(), 497 static_cast<int>(image.getWidth()), 498 endPos.getY() + static_cast<int>(image.getHeight()) - startPos.getY()); 499 } 500 } 501 502 DISTRHO_DECLARE_NON_COPYABLE(PrivateData) 503 }; 504 505 // -------------------------------------------------------------------------------------------------------------------- 506 507 template <class ImageType> 508 ImageBaseSlider<ImageType>::ImageBaseSlider(Widget* const parentWidget, const ImageType& image) noexcept 509 : SubWidget(parentWidget), 510 pData(new PrivateData(image)) 511 { 512 setNeedsFullViewportDrawing(); 513 } 514 515 template <class ImageType> 516 ImageBaseSlider<ImageType>::~ImageBaseSlider() 517 { 518 delete pData; 519 } 520 521 template <class ImageType> 522 float ImageBaseSlider<ImageType>::getValue() const noexcept 523 { 524 return pData->value; 525 } 526 527 template <class ImageType> 528 void ImageBaseSlider<ImageType>::setValue(float value, bool sendCallback) noexcept 529 { 530 if (! pData->valueIsSet) 531 pData->valueIsSet = true; 532 533 if (d_isEqual(pData->value, value)) 534 return; 535 536 pData->value = value; 537 538 if (d_isZero(pData->step)) 539 pData->valueTmp = value; 540 541 repaint(); 542 543 if (sendCallback && pData->callback != nullptr) 544 { 545 try { 546 pData->callback->imageSliderValueChanged(this, pData->value); 547 } DISTRHO_SAFE_EXCEPTION("ImageBaseSlider::setValue"); 548 } 549 } 550 551 template <class ImageType> 552 void ImageBaseSlider<ImageType>::setStartPos(const Point<int>& startPos) noexcept 553 { 554 pData->startPos = startPos; 555 pData->recheckArea(); 556 } 557 558 template <class ImageType> 559 void ImageBaseSlider<ImageType>::setStartPos(int x, int y) noexcept 560 { 561 setStartPos(Point<int>(x, y)); 562 } 563 564 template <class ImageType> 565 void ImageBaseSlider<ImageType>::setEndPos(const Point<int>& endPos) noexcept 566 { 567 pData->endPos = endPos; 568 pData->recheckArea(); 569 } 570 571 template <class ImageType> 572 void ImageBaseSlider<ImageType>::setEndPos(int x, int y) noexcept 573 { 574 setEndPos(Point<int>(x, y)); 575 } 576 577 template <class ImageType> 578 void ImageBaseSlider<ImageType>::setCheckable(bool checkable) noexcept 579 { 580 if (pData->checkable == checkable) 581 return; 582 583 pData->checkable = checkable; 584 repaint(); 585 } 586 587 template <class ImageType> 588 void ImageBaseSlider<ImageType>::setInverted(bool inverted) noexcept 589 { 590 if (pData->inverted == inverted) 591 return; 592 593 pData->inverted = inverted; 594 repaint(); 595 } 596 597 template <class ImageType> 598 void ImageBaseSlider<ImageType>::setDefault(float value) noexcept 599 { 600 pData->valueDef = value; 601 pData->usingDefault = true; 602 } 603 604 template <class ImageType> 605 void ImageBaseSlider<ImageType>::setRange(float min, float max) noexcept 606 { 607 pData->minimum = min; 608 pData->maximum = max; 609 610 if (pData->value < min) 611 { 612 pData->value = min; 613 repaint(); 614 615 if (pData->callback != nullptr && pData->valueIsSet) 616 { 617 try { 618 pData->callback->imageSliderValueChanged(this, pData->value); 619 } DISTRHO_SAFE_EXCEPTION("ImageBaseSlider::setRange < min"); 620 } 621 } 622 else if (pData->value > max) 623 { 624 pData->value = max; 625 repaint(); 626 627 if (pData->callback != nullptr && pData->valueIsSet) 628 { 629 try { 630 pData->callback->imageSliderValueChanged(this, pData->value); 631 } DISTRHO_SAFE_EXCEPTION("ImageBaseSlider::setRange > max"); 632 } 633 } 634 } 635 636 template <class ImageType> 637 void ImageBaseSlider<ImageType>::setStep(float step) noexcept 638 { 639 pData->step = step; 640 } 641 642 template <class ImageType> 643 void ImageBaseSlider<ImageType>::setCallback(Callback* callback) noexcept 644 { 645 pData->callback = callback; 646 } 647 648 template <class ImageType> 649 void ImageBaseSlider<ImageType>::onDisplay() 650 { 651 const GraphicsContext& context(getGraphicsContext()); 652 653 #if 0 // DEBUG, paints slider area 654 Color(1.0f, 1.0f, 1.0f, 0.5f).setFor(context, true); 655 Rectangle<int>(pData->sliderArea.getX(), 656 pData->sliderArea.getY(), 657 pData->sliderArea.getX()+pData->sliderArea.getWidth(), 658 pData->sliderArea.getY()+pData->sliderArea.getHeight()).draw(context); 659 Color(1.0f, 1.0f, 1.0f, 1.0f).setFor(context, true); 660 #endif 661 662 const float normValue = (pData->value - pData->minimum) / (pData->maximum - pData->minimum); 663 664 int x, y; 665 666 if (pData->startPos.getY() == pData->endPos.getY()) 667 { 668 // horizontal 669 if (pData->inverted) 670 x = pData->endPos.getX() - static_cast<int>(normValue*static_cast<float>(pData->endPos.getX()-pData->startPos.getX())); 671 else 672 x = pData->startPos.getX() + static_cast<int>(normValue*static_cast<float>(pData->endPos.getX()-pData->startPos.getX())); 673 674 y = pData->startPos.getY(); 675 } 676 else 677 { 678 // vertical 679 x = pData->startPos.getX(); 680 681 if (pData->inverted) 682 y = pData->endPos.getY() - static_cast<int>(normValue*static_cast<float>(pData->endPos.getY()-pData->startPos.getY())); 683 else 684 y = pData->startPos.getY() + static_cast<int>(normValue*static_cast<float>(pData->endPos.getY()-pData->startPos.getY())); 685 } 686 687 pData->image.drawAt(context, x, y); 688 } 689 690 template <class ImageType> 691 bool ImageBaseSlider<ImageType>::onMouse(const MouseEvent& ev) 692 { 693 if (ev.button != 1) 694 return false; 695 696 if (ev.press) 697 { 698 if (! pData->sliderArea.contains(ev.pos)) 699 return false; 700 701 if ((ev.mod & kModifierShift) != 0 && pData->usingDefault) 702 { 703 setValue(pData->valueDef, true); 704 pData->valueTmp = pData->value; 705 return true; 706 } 707 708 if (pData->checkable) 709 { 710 const float value = d_isEqual(pData->valueTmp, pData->minimum) ? pData->maximum : pData->minimum; 711 setValue(value, true); 712 pData->valueTmp = pData->value; 713 return true; 714 } 715 716 float vper; 717 const double x = ev.pos.getX(); 718 const double y = ev.pos.getY(); 719 720 if (pData->startPos.getY() == pData->endPos.getY()) 721 { 722 // horizontal 723 vper = float(x - pData->sliderArea.getX()) / float(pData->sliderArea.getWidth()); 724 } 725 else 726 { 727 // vertical 728 vper = float(y - pData->sliderArea.getY()) / float(pData->sliderArea.getHeight()); 729 } 730 731 float value; 732 733 if (pData->inverted) 734 value = pData->maximum - vper * (pData->maximum - pData->minimum); 735 else 736 value = pData->minimum + vper * (pData->maximum - pData->minimum); 737 738 if (value < pData->minimum) 739 { 740 pData->valueTmp = value = pData->minimum; 741 } 742 else if (value > pData->maximum) 743 { 744 pData->valueTmp = value = pData->maximum; 745 } 746 else if (d_isNotZero(pData->step)) 747 { 748 pData->valueTmp = value; 749 const float rest = std::fmod(value, pData->step); 750 value = value - rest + (rest > pData->step/2.0f ? pData->step : 0.0f); 751 } 752 753 pData->dragging = true; 754 pData->startedX = x; 755 pData->startedY = y; 756 757 if (pData->callback != nullptr) 758 pData->callback->imageSliderDragStarted(this); 759 760 setValue(value, true); 761 762 return true; 763 } 764 else if (pData->dragging) 765 { 766 if (pData->callback != nullptr) 767 pData->callback->imageSliderDragFinished(this); 768 769 pData->dragging = false; 770 return true; 771 } 772 773 return false; 774 } 775 776 template <class ImageType> 777 bool ImageBaseSlider<ImageType>::onMotion(const MotionEvent& ev) 778 { 779 if (! pData->dragging) 780 return false; 781 782 const bool horizontal = pData->startPos.getY() == pData->endPos.getY(); 783 const double x = ev.pos.getX(); 784 const double y = ev.pos.getY(); 785 786 if ((horizontal && pData->sliderArea.containsX(x)) || (pData->sliderArea.containsY(y) && ! horizontal)) 787 { 788 float vper; 789 790 if (horizontal) 791 { 792 // horizontal 793 vper = float(x - pData->sliderArea.getX()) / float(pData->sliderArea.getWidth()); 794 } 795 else 796 { 797 // vertical 798 vper = float(y - pData->sliderArea.getY()) / float(pData->sliderArea.getHeight()); 799 } 800 801 float value; 802 803 if (pData->inverted) 804 value = pData->maximum - vper * (pData->maximum - pData->minimum); 805 else 806 value = pData->minimum + vper * (pData->maximum - pData->minimum); 807 808 if (value < pData->minimum) 809 { 810 pData->valueTmp = value = pData->minimum; 811 } 812 else if (value > pData->maximum) 813 { 814 pData->valueTmp = value = pData->maximum; 815 } 816 else if (d_isNotZero(pData->step)) 817 { 818 pData->valueTmp = value; 819 const float rest = std::fmod(value, pData->step); 820 value = value - rest + (rest > pData->step/2.0f ? pData->step : 0.0f); 821 } 822 823 setValue(value, true); 824 } 825 else if (horizontal) 826 { 827 if (x < pData->sliderArea.getX()) 828 setValue(pData->inverted ? pData->maximum : pData->minimum, true); 829 else 830 setValue(pData->inverted ? pData->minimum : pData->maximum, true); 831 } 832 else 833 { 834 if (y < pData->sliderArea.getY()) 835 setValue(pData->inverted ? pData->maximum : pData->minimum, true); 836 else 837 setValue(pData->inverted ? pData->minimum : pData->maximum, true); 838 } 839 840 return true; 841 } 842 843 // -------------------------------------------------------------------------------------------------------------------- 844 845 template <class ImageType> 846 struct ImageBaseSwitch<ImageType>::PrivateData { 847 ImageType imageNormal; 848 ImageType imageDown; 849 bool isDown; 850 Callback* callback; 851 852 PrivateData(const ImageType& normal, const ImageType& down) 853 : imageNormal(normal), 854 imageDown(down), 855 isDown(false), 856 callback(nullptr) 857 { 858 DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize()); 859 } 860 861 PrivateData(PrivateData* const other) 862 : imageNormal(other->imageNormal), 863 imageDown(other->imageDown), 864 isDown(other->isDown), 865 callback(other->callback) 866 { 867 DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize()); 868 } 869 870 void assignFrom(PrivateData* const other) 871 { 872 imageNormal = other->imageNormal; 873 imageDown = other->imageDown; 874 isDown = other->isDown; 875 callback = other->callback; 876 DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize()); 877 } 878 879 DISTRHO_DECLARE_NON_COPYABLE(PrivateData) 880 }; 881 882 // -------------------------------------------------------------------------------------------------------------------- 883 884 template <class ImageType> 885 ImageBaseSwitch<ImageType>::ImageBaseSwitch(Widget* const parentWidget, const ImageType& imageNormal, const ImageType& imageDown) noexcept 886 : SubWidget(parentWidget), 887 pData(new PrivateData(imageNormal, imageDown)) 888 { 889 setSize(imageNormal.getSize()); 890 } 891 892 template <class ImageType> 893 ImageBaseSwitch<ImageType>::ImageBaseSwitch(const ImageBaseSwitch<ImageType>& imageSwitch) noexcept 894 : SubWidget(imageSwitch.getParentWidget()), 895 pData(new PrivateData(imageSwitch.pData)) 896 { 897 setSize(pData->imageNormal.getSize()); 898 } 899 900 template <class ImageType> 901 ImageBaseSwitch<ImageType>& ImageBaseSwitch<ImageType>::operator=(const ImageBaseSwitch<ImageType>& imageSwitch) noexcept 902 { 903 pData->assignFrom(imageSwitch.pData); 904 setSize(pData->imageNormal.getSize()); 905 return *this; 906 } 907 908 template <class ImageType> 909 ImageBaseSwitch<ImageType>::~ImageBaseSwitch() 910 { 911 delete pData; 912 } 913 914 template <class ImageType> 915 bool ImageBaseSwitch<ImageType>::isDown() const noexcept 916 { 917 return pData->isDown; 918 } 919 920 template <class ImageType> 921 void ImageBaseSwitch<ImageType>::setDown(const bool down) noexcept 922 { 923 if (pData->isDown == down) 924 return; 925 926 pData->isDown = down; 927 repaint(); 928 } 929 930 template <class ImageType> 931 void ImageBaseSwitch<ImageType>::setCallback(Callback* const callback) noexcept 932 { 933 pData->callback = callback; 934 } 935 936 template <class ImageType> 937 void ImageBaseSwitch<ImageType>::onDisplay() 938 { 939 const GraphicsContext& context(getGraphicsContext()); 940 941 if (pData->isDown) 942 pData->imageDown.draw(context); 943 else 944 pData->imageNormal.draw(context); 945 } 946 947 template <class ImageType> 948 bool ImageBaseSwitch<ImageType>::onMouse(const MouseEvent& ev) 949 { 950 if (ev.press && contains(ev.pos)) 951 { 952 pData->isDown = !pData->isDown; 953 954 repaint(); 955 956 if (pData->callback != nullptr) 957 pData->callback->imageSwitchClicked(this, pData->isDown); 958 959 return true; 960 } 961 962 return false; 963 } 964 965 // -------------------------------------------------------------------------------------------------------------------- 966 967 END_NAMESPACE_DGL