DISTRHO Plugin Framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

914 lines
25KB

  1. /*
  2. * DISTRHO Plugin Framework (DPF)
  3. * Copyright (C) 2012-2021 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. #include "../ImageBaseWidgets.hpp"
  17. #include "../Color.hpp"
  18. START_NAMESPACE_DGL
  19. // --------------------------------------------------------------------------------------------------------------------
  20. template <class ImageType>
  21. ImageBaseAboutWindow<ImageType>::ImageBaseAboutWindow(Window& parentWindow, const ImageType& image)
  22. : StandaloneWindow(parentWindow.getApp(), parentWindow),
  23. img(image)
  24. {
  25. setResizable(false);
  26. setTitle("About");
  27. if (image.isValid())
  28. setSize(image.getSize());
  29. }
  30. template <class ImageType>
  31. ImageBaseAboutWindow<ImageType>::ImageBaseAboutWindow(TopLevelWidget* const parentTopLevelWidget, const ImageType& image)
  32. : StandaloneWindow(parentTopLevelWidget->getApp(), parentTopLevelWidget->getWindow()),
  33. img(image)
  34. {
  35. setResizable(false);
  36. setTitle("About");
  37. if (image.isValid())
  38. setSize(image.getSize());
  39. }
  40. template <class ImageType>
  41. void ImageBaseAboutWindow<ImageType>::setImage(const ImageType& image)
  42. {
  43. if (img == image)
  44. return;
  45. img = image;
  46. setSize(image.getSize());
  47. }
  48. template <class ImageType>
  49. void ImageBaseAboutWindow<ImageType>::onDisplay()
  50. {
  51. img.draw(getGraphicsContext());
  52. }
  53. template <class ImageType>
  54. bool ImageBaseAboutWindow<ImageType>::onKeyboard(const KeyboardEvent& ev)
  55. {
  56. if (ev.press && ev.key == kKeyEscape)
  57. {
  58. close();
  59. return true;
  60. }
  61. return false;
  62. }
  63. template <class ImageType>
  64. bool ImageBaseAboutWindow<ImageType>::onMouse(const MouseEvent& ev)
  65. {
  66. if (ev.press)
  67. {
  68. close();
  69. return true;
  70. }
  71. return false;
  72. }
  73. // --------------------------------------------------------------------------------------------------------------------
  74. template <class ImageType>
  75. struct ImageBaseButton<ImageType>::PrivateData : public ButtonEventHandler::Callback {
  76. ImageBaseButton<ImageType>::Callback* callback;
  77. ImageType imageNormal;
  78. ImageType imageHover;
  79. ImageType imageDown;
  80. PrivateData(const ImageType& normal, const ImageType& hover, const ImageType& down)
  81. : callback(nullptr),
  82. imageNormal(normal),
  83. imageHover(hover),
  84. imageDown(down) {}
  85. void buttonClicked(SubWidget* widget, int button) override
  86. {
  87. if (callback != nullptr)
  88. if (ImageBaseButton* const imageButton = dynamic_cast<ImageBaseButton*>(widget))
  89. callback->imageButtonClicked(imageButton, button);
  90. }
  91. DISTRHO_DECLARE_NON_COPYABLE(PrivateData)
  92. };
  93. // --------------------------------------------------------------------------------------------------------------------
  94. template <class ImageType>
  95. ImageBaseButton<ImageType>::ImageBaseButton(Widget* const parentWidget, const ImageType& image)
  96. : SubWidget(parentWidget),
  97. ButtonEventHandler(this),
  98. pData(new PrivateData(image, image, image))
  99. {
  100. ButtonEventHandler::setCallback(pData);
  101. setSize(image.getSize());
  102. }
  103. template <class ImageType>
  104. ImageBaseButton<ImageType>::ImageBaseButton(Widget* const parentWidget, const ImageType& imageNormal, const ImageType& imageDown)
  105. : SubWidget(parentWidget),
  106. ButtonEventHandler(this),
  107. pData(new PrivateData(imageNormal, imageNormal, imageDown))
  108. {
  109. DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize());
  110. ButtonEventHandler::setCallback(pData);
  111. setSize(imageNormal.getSize());
  112. }
  113. template <class ImageType>
  114. ImageBaseButton<ImageType>::ImageBaseButton(Widget* const parentWidget, const ImageType& imageNormal, const ImageType& imageHover, const ImageType& imageDown)
  115. : SubWidget(parentWidget),
  116. ButtonEventHandler(this),
  117. pData(new PrivateData(imageNormal, imageHover, imageDown))
  118. {
  119. DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageHover.getSize() && imageHover.getSize() == imageDown.getSize());
  120. ButtonEventHandler::setCallback(pData);
  121. setSize(imageNormal.getSize());
  122. }
  123. template <class ImageType>
  124. ImageBaseButton<ImageType>::~ImageBaseButton()
  125. {
  126. delete pData;
  127. }
  128. template <class ImageType>
  129. void ImageBaseButton<ImageType>::setCallback(Callback* callback) noexcept
  130. {
  131. pData->callback = callback;
  132. }
  133. template <class ImageType>
  134. void ImageBaseButton<ImageType>::onDisplay()
  135. {
  136. const GraphicsContext& context(getGraphicsContext());
  137. const State state = ButtonEventHandler::getState();
  138. if (state & kButtonStateActive)
  139. pData->imageDown.draw(context);
  140. else if (state & kButtonStateHover)
  141. pData->imageHover.draw(context);
  142. else
  143. pData->imageNormal.draw(context);
  144. }
  145. template <class ImageType>
  146. bool ImageBaseButton<ImageType>::onMouse(const MouseEvent& ev)
  147. {
  148. if (SubWidget::onMouse(ev))
  149. return true;
  150. return ButtonEventHandler::mouseEvent(ev);
  151. }
  152. template <class ImageType>
  153. bool ImageBaseButton<ImageType>::onMotion(const MotionEvent& ev)
  154. {
  155. if (SubWidget::onMotion(ev))
  156. return true;
  157. return ButtonEventHandler::motionEvent(ev);
  158. }
  159. // --------------------------------------------------------------------------------------------------------------------
  160. template <class ImageType>
  161. struct ImageBaseKnob<ImageType>::PrivateData : public KnobEventHandler::Callback {
  162. ImageBaseKnob<ImageType>::Callback* callback;
  163. ImageType image;
  164. int rotationAngle;
  165. bool alwaysRepaint;
  166. bool isImgVertical;
  167. uint imgLayerWidth;
  168. uint imgLayerHeight;
  169. uint imgLayerCount;
  170. bool isReady;
  171. union {
  172. uint glTextureId;
  173. void* cairoSurface;
  174. };
  175. explicit PrivateData(const ImageType& img)
  176. : callback(nullptr),
  177. image(img),
  178. rotationAngle(0),
  179. alwaysRepaint(false),
  180. isImgVertical(img.getHeight() > img.getWidth()),
  181. imgLayerWidth(isImgVertical ? img.getWidth() : img.getHeight()),
  182. imgLayerHeight(imgLayerWidth),
  183. imgLayerCount(isImgVertical ? img.getHeight()/imgLayerHeight : img.getWidth()/imgLayerWidth),
  184. isReady(false)
  185. {
  186. init();
  187. }
  188. explicit PrivateData(PrivateData* const other)
  189. : callback(other->callback),
  190. image(other->image),
  191. rotationAngle(other->rotationAngle),
  192. alwaysRepaint(other->alwaysRepaint),
  193. isImgVertical(other->isImgVertical),
  194. imgLayerWidth(other->imgLayerWidth),
  195. imgLayerHeight(other->imgLayerHeight),
  196. imgLayerCount(other->imgLayerCount),
  197. isReady(false)
  198. {
  199. init();
  200. }
  201. void assignFrom(PrivateData* const other)
  202. {
  203. cleanup();
  204. image = other->image;
  205. rotationAngle = other->rotationAngle;
  206. callback = other->callback;
  207. alwaysRepaint = other->alwaysRepaint;
  208. isImgVertical = other->isImgVertical;
  209. imgLayerWidth = other->imgLayerWidth;
  210. imgLayerHeight = other->imgLayerHeight;
  211. imgLayerCount = other->imgLayerCount;
  212. isReady = false;
  213. init();
  214. }
  215. ~PrivateData()
  216. {
  217. cleanup();
  218. }
  219. void knobDragStarted(SubWidget* const widget) override
  220. {
  221. if (callback != nullptr)
  222. if (ImageBaseKnob* const imageKnob = dynamic_cast<ImageBaseKnob*>(widget))
  223. callback->imageKnobDragStarted(imageKnob);
  224. }
  225. void knobDragFinished(SubWidget* const widget) override
  226. {
  227. if (callback != nullptr)
  228. if (ImageBaseKnob* const imageKnob = dynamic_cast<ImageBaseKnob*>(widget))
  229. callback->imageKnobDragFinished(imageKnob);
  230. }
  231. void knobValueChanged(SubWidget* const widget, const float value) override
  232. {
  233. if (rotationAngle == 0 || alwaysRepaint)
  234. isReady = false;
  235. if (callback != nullptr)
  236. if (ImageBaseKnob* const imageKnob = dynamic_cast<ImageBaseKnob*>(widget))
  237. callback->imageKnobValueChanged(imageKnob, value);
  238. }
  239. // implemented independently per graphics backend
  240. void init();
  241. void cleanup();
  242. DISTRHO_DECLARE_NON_COPYABLE(PrivateData)
  243. };
  244. // --------------------------------------------------------------------------------------------------------------------
  245. template <class ImageType>
  246. ImageBaseKnob<ImageType>::ImageBaseKnob(Widget* const parentWidget,
  247. const ImageType& image,
  248. const Orientation orientation) noexcept
  249. : SubWidget(parentWidget),
  250. KnobEventHandler(this),
  251. pData(new PrivateData(image))
  252. {
  253. KnobEventHandler::setCallback(pData);
  254. setOrientation(orientation);
  255. setSize(pData->imgLayerWidth, pData->imgLayerHeight);
  256. }
  257. template <class ImageType>
  258. ImageBaseKnob<ImageType>::ImageBaseKnob(const ImageBaseKnob<ImageType>& imageKnob)
  259. : SubWidget(imageKnob.getParentWidget()),
  260. KnobEventHandler(this, imageKnob),
  261. pData(new PrivateData(imageKnob.pData))
  262. {
  263. KnobEventHandler::setCallback(pData);
  264. setOrientation(imageKnob.getOrientation());
  265. setSize(pData->imgLayerWidth, pData->imgLayerHeight);
  266. }
  267. template <class ImageType>
  268. ImageBaseKnob<ImageType>& ImageBaseKnob<ImageType>::operator=(const ImageBaseKnob<ImageType>& imageKnob)
  269. {
  270. KnobEventHandler::operator=(imageKnob);
  271. pData->assignFrom(imageKnob.pData);
  272. setSize(pData->imgLayerWidth, pData->imgLayerHeight);
  273. return *this;
  274. }
  275. template <class ImageType>
  276. ImageBaseKnob<ImageType>::~ImageBaseKnob()
  277. {
  278. delete pData;
  279. }
  280. template <class ImageType>
  281. void ImageBaseKnob<ImageType>::setCallback(Callback* callback) noexcept
  282. {
  283. pData->callback = callback;
  284. }
  285. template <class ImageType>
  286. void ImageBaseKnob<ImageType>::setImageLayerCount(uint count) noexcept
  287. {
  288. DISTRHO_SAFE_ASSERT_RETURN(count > 1,);
  289. pData->imgLayerCount = count;
  290. if (pData->isImgVertical)
  291. pData->imgLayerHeight = pData->image.getHeight()/count;
  292. else
  293. pData->imgLayerWidth = pData->image.getWidth()/count;
  294. setSize(pData->imgLayerWidth, pData->imgLayerHeight);
  295. }
  296. template <class ImageType>
  297. void ImageBaseKnob<ImageType>::setRotationAngle(int angle)
  298. {
  299. if (pData->rotationAngle == angle)
  300. return;
  301. pData->rotationAngle = angle;
  302. pData->isReady = false;
  303. }
  304. template <class ImageType>
  305. bool ImageBaseKnob<ImageType>::setValue(float value, bool sendCallback) noexcept
  306. {
  307. if (KnobEventHandler::setValue(value, sendCallback))
  308. {
  309. if (pData->rotationAngle == 0 || pData->alwaysRepaint)
  310. pData->isReady = false;
  311. return true;
  312. }
  313. return false;
  314. }
  315. template <class ImageType>
  316. bool ImageBaseKnob<ImageType>::onMouse(const MouseEvent& ev)
  317. {
  318. if (SubWidget::onMouse(ev))
  319. return true;
  320. return KnobEventHandler::mouseEvent(ev);
  321. }
  322. template <class ImageType>
  323. bool ImageBaseKnob<ImageType>::onMotion(const MotionEvent& ev)
  324. {
  325. if (SubWidget::onMotion(ev))
  326. return true;
  327. return KnobEventHandler::motionEvent(ev);
  328. }
  329. template <class ImageType>
  330. bool ImageBaseKnob<ImageType>::onScroll(const ScrollEvent& ev)
  331. {
  332. if (SubWidget::onScroll(ev))
  333. return true;
  334. return KnobEventHandler::scrollEvent(ev);
  335. }
  336. // --------------------------------------------------------------------------------------------------------------------
  337. template <class ImageType>
  338. struct ImageBaseSlider<ImageType>::PrivateData {
  339. ImageType image;
  340. float minimum;
  341. float maximum;
  342. float step;
  343. float value;
  344. float valueDef;
  345. float valueTmp;
  346. bool usingDefault;
  347. bool dragging;
  348. bool inverted;
  349. bool valueIsSet;
  350. double startedX;
  351. double startedY;
  352. Callback* callback;
  353. Point<int> startPos;
  354. Point<int> endPos;
  355. Rectangle<double> sliderArea;
  356. PrivateData(const ImageType& img)
  357. : image(img),
  358. minimum(0.0f),
  359. maximum(1.0f),
  360. step(0.0f),
  361. value(0.5f),
  362. valueDef(value),
  363. valueTmp(value),
  364. usingDefault(false),
  365. dragging(false),
  366. inverted(false),
  367. valueIsSet(false),
  368. startedX(0.0),
  369. startedY(0.0),
  370. callback(nullptr),
  371. startPos(),
  372. endPos(),
  373. sliderArea() {}
  374. void recheckArea() noexcept
  375. {
  376. if (startPos.getY() == endPos.getY())
  377. {
  378. // horizontal
  379. sliderArea = Rectangle<double>(startPos.getX(),
  380. startPos.getY(),
  381. endPos.getX() + static_cast<int>(image.getWidth()) - startPos.getX(),
  382. static_cast<int>(image.getHeight()));
  383. }
  384. else
  385. {
  386. // vertical
  387. sliderArea = Rectangle<double>(startPos.getX(),
  388. startPos.getY(),
  389. static_cast<int>(image.getWidth()),
  390. endPos.getY() + static_cast<int>(image.getHeight()) - startPos.getY());
  391. }
  392. }
  393. DISTRHO_DECLARE_NON_COPYABLE(PrivateData)
  394. };
  395. // --------------------------------------------------------------------------------------------------------------------
  396. template <class ImageType>
  397. ImageBaseSlider<ImageType>::ImageBaseSlider(Widget* const parentWidget, const ImageType& image) noexcept
  398. : SubWidget(parentWidget),
  399. pData(new PrivateData(image))
  400. {
  401. setNeedsFullViewportDrawing();
  402. }
  403. template <class ImageType>
  404. ImageBaseSlider<ImageType>::~ImageBaseSlider()
  405. {
  406. delete pData;
  407. }
  408. template <class ImageType>
  409. float ImageBaseSlider<ImageType>::getValue() const noexcept
  410. {
  411. return pData->value;
  412. }
  413. template <class ImageType>
  414. void ImageBaseSlider<ImageType>::setValue(float value, bool sendCallback) noexcept
  415. {
  416. if (! pData->valueIsSet)
  417. pData->valueIsSet = true;
  418. if (d_isEqual(pData->value, value))
  419. return;
  420. pData->value = value;
  421. if (d_isZero(pData->step))
  422. pData->valueTmp = value;
  423. repaint();
  424. if (sendCallback && pData->callback != nullptr)
  425. {
  426. try {
  427. pData->callback->imageSliderValueChanged(this, pData->value);
  428. } DISTRHO_SAFE_EXCEPTION("ImageBaseSlider::setValue");
  429. }
  430. }
  431. template <class ImageType>
  432. void ImageBaseSlider<ImageType>::setStartPos(const Point<int>& startPos) noexcept
  433. {
  434. pData->startPos = startPos;
  435. pData->recheckArea();
  436. }
  437. template <class ImageType>
  438. void ImageBaseSlider<ImageType>::setStartPos(int x, int y) noexcept
  439. {
  440. setStartPos(Point<int>(x, y));
  441. }
  442. template <class ImageType>
  443. void ImageBaseSlider<ImageType>::setEndPos(const Point<int>& endPos) noexcept
  444. {
  445. pData->endPos = endPos;
  446. pData->recheckArea();
  447. }
  448. template <class ImageType>
  449. void ImageBaseSlider<ImageType>::setEndPos(int x, int y) noexcept
  450. {
  451. setEndPos(Point<int>(x, y));
  452. }
  453. template <class ImageType>
  454. void ImageBaseSlider<ImageType>::setInverted(bool inverted) noexcept
  455. {
  456. if (pData->inverted == inverted)
  457. return;
  458. pData->inverted = inverted;
  459. repaint();
  460. }
  461. template <class ImageType>
  462. void ImageBaseSlider<ImageType>::setDefault(float value) noexcept
  463. {
  464. pData->valueDef = value;
  465. pData->usingDefault = true;
  466. }
  467. template <class ImageType>
  468. void ImageBaseSlider<ImageType>::setRange(float min, float max) noexcept
  469. {
  470. pData->minimum = min;
  471. pData->maximum = max;
  472. if (pData->value < min)
  473. {
  474. pData->value = min;
  475. repaint();
  476. if (pData->callback != nullptr && pData->valueIsSet)
  477. {
  478. try {
  479. pData->callback->imageSliderValueChanged(this, pData->value);
  480. } DISTRHO_SAFE_EXCEPTION("ImageBaseSlider::setRange < min");
  481. }
  482. }
  483. else if (pData->value > max)
  484. {
  485. pData->value = max;
  486. repaint();
  487. if (pData->callback != nullptr && pData->valueIsSet)
  488. {
  489. try {
  490. pData->callback->imageSliderValueChanged(this, pData->value);
  491. } DISTRHO_SAFE_EXCEPTION("ImageBaseSlider::setRange > max");
  492. }
  493. }
  494. }
  495. template <class ImageType>
  496. void ImageBaseSlider<ImageType>::setStep(float step) noexcept
  497. {
  498. pData->step = step;
  499. }
  500. template <class ImageType>
  501. void ImageBaseSlider<ImageType>::setCallback(Callback* callback) noexcept
  502. {
  503. pData->callback = callback;
  504. }
  505. template <class ImageType>
  506. void ImageBaseSlider<ImageType>::onDisplay()
  507. {
  508. const GraphicsContext& context(getGraphicsContext());
  509. #if 0 // DEBUG, paints slider area
  510. Color(1.0f, 1.0f, 1.0f, 0.5f).setFor(context, true);
  511. Rectangle<int>(pData->sliderArea.getX(),
  512. pData->sliderArea.getY(),
  513. pData->sliderArea.getX()+pData->sliderArea.getWidth(),
  514. pData->sliderArea.getY()+pData->sliderArea.getHeight()).draw(context);
  515. Color(1.0f, 1.0f, 1.0f, 1.0f).setFor(context, true);
  516. #endif
  517. const float normValue = (pData->value - pData->minimum) / (pData->maximum - pData->minimum);
  518. int x, y;
  519. if (pData->startPos.getY() == pData->endPos.getY())
  520. {
  521. // horizontal
  522. if (pData->inverted)
  523. x = pData->endPos.getX() - static_cast<int>(normValue*static_cast<float>(pData->endPos.getX()-pData->startPos.getX()));
  524. else
  525. x = pData->startPos.getX() + static_cast<int>(normValue*static_cast<float>(pData->endPos.getX()-pData->startPos.getX()));
  526. y = pData->startPos.getY();
  527. }
  528. else
  529. {
  530. // vertical
  531. x = pData->startPos.getX();
  532. if (pData->inverted)
  533. y = pData->endPos.getY() - static_cast<int>(normValue*static_cast<float>(pData->endPos.getY()-pData->startPos.getY()));
  534. else
  535. y = pData->startPos.getY() + static_cast<int>(normValue*static_cast<float>(pData->endPos.getY()-pData->startPos.getY()));
  536. }
  537. pData->image.drawAt(context, x, y);
  538. }
  539. template <class ImageType>
  540. bool ImageBaseSlider<ImageType>::onMouse(const MouseEvent& ev)
  541. {
  542. if (ev.button != 1)
  543. return false;
  544. if (ev.press)
  545. {
  546. if (! pData->sliderArea.contains(ev.pos))
  547. return false;
  548. if ((ev.mod & kModifierShift) != 0 && pData->usingDefault)
  549. {
  550. setValue(pData->valueDef, true);
  551. pData->valueTmp = pData->value;
  552. return true;
  553. }
  554. float vper;
  555. const double x = ev.pos.getX();
  556. const double y = ev.pos.getY();
  557. if (pData->startPos.getY() == pData->endPos.getY())
  558. {
  559. // horizontal
  560. vper = float(x - pData->sliderArea.getX()) / float(pData->sliderArea.getWidth());
  561. }
  562. else
  563. {
  564. // vertical
  565. vper = float(y - pData->sliderArea.getY()) / float(pData->sliderArea.getHeight());
  566. }
  567. float value;
  568. if (pData->inverted)
  569. value = pData->maximum - vper * (pData->maximum - pData->minimum);
  570. else
  571. value = pData->minimum + vper * (pData->maximum - pData->minimum);
  572. if (value < pData->minimum)
  573. {
  574. pData->valueTmp = value = pData->minimum;
  575. }
  576. else if (value > pData->maximum)
  577. {
  578. pData->valueTmp = value = pData->maximum;
  579. }
  580. else if (d_isNotZero(pData->step))
  581. {
  582. pData->valueTmp = value;
  583. const float rest = std::fmod(value, pData->step);
  584. value = value - rest + (rest > pData->step/2.0f ? pData->step : 0.0f);
  585. }
  586. pData->dragging = true;
  587. pData->startedX = x;
  588. pData->startedY = y;
  589. if (pData->callback != nullptr)
  590. pData->callback->imageSliderDragStarted(this);
  591. setValue(value, true);
  592. return true;
  593. }
  594. else if (pData->dragging)
  595. {
  596. if (pData->callback != nullptr)
  597. pData->callback->imageSliderDragFinished(this);
  598. pData->dragging = false;
  599. return true;
  600. }
  601. return false;
  602. }
  603. template <class ImageType>
  604. bool ImageBaseSlider<ImageType>::onMotion(const MotionEvent& ev)
  605. {
  606. if (! pData->dragging)
  607. return false;
  608. const bool horizontal = pData->startPos.getY() == pData->endPos.getY();
  609. const double x = ev.pos.getX();
  610. const double y = ev.pos.getY();
  611. if ((horizontal && pData->sliderArea.containsX(x)) || (pData->sliderArea.containsY(y) && ! horizontal))
  612. {
  613. float vper;
  614. if (horizontal)
  615. {
  616. // horizontal
  617. vper = float(x - pData->sliderArea.getX()) / float(pData->sliderArea.getWidth());
  618. }
  619. else
  620. {
  621. // vertical
  622. vper = float(y - pData->sliderArea.getY()) / float(pData->sliderArea.getHeight());
  623. }
  624. float value;
  625. if (pData->inverted)
  626. value = pData->maximum - vper * (pData->maximum - pData->minimum);
  627. else
  628. value = pData->minimum + vper * (pData->maximum - pData->minimum);
  629. if (value < pData->minimum)
  630. {
  631. pData->valueTmp = value = pData->minimum;
  632. }
  633. else if (value > pData->maximum)
  634. {
  635. pData->valueTmp = value = pData->maximum;
  636. }
  637. else if (d_isNotZero(pData->step))
  638. {
  639. pData->valueTmp = value;
  640. const float rest = std::fmod(value, pData->step);
  641. value = value - rest + (rest > pData->step/2.0f ? pData->step : 0.0f);
  642. }
  643. setValue(value, true);
  644. }
  645. else if (horizontal)
  646. {
  647. if (x < pData->sliderArea.getX())
  648. setValue(pData->inverted ? pData->maximum : pData->minimum, true);
  649. else
  650. setValue(pData->inverted ? pData->minimum : pData->maximum, true);
  651. }
  652. else
  653. {
  654. if (y < pData->sliderArea.getY())
  655. setValue(pData->inverted ? pData->maximum : pData->minimum, true);
  656. else
  657. setValue(pData->inverted ? pData->minimum : pData->maximum, true);
  658. }
  659. return true;
  660. }
  661. // --------------------------------------------------------------------------------------------------------------------
  662. template <class ImageType>
  663. struct ImageBaseSwitch<ImageType>::PrivateData {
  664. ImageType imageNormal;
  665. ImageType imageDown;
  666. bool isDown;
  667. Callback* callback;
  668. PrivateData(const ImageType& normal, const ImageType& down)
  669. : imageNormal(normal),
  670. imageDown(down),
  671. isDown(false),
  672. callback(nullptr)
  673. {
  674. DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize());
  675. }
  676. PrivateData(PrivateData* const other)
  677. : imageNormal(other->imageNormal),
  678. imageDown(other->imageDown),
  679. isDown(other->isDown),
  680. callback(other->callback)
  681. {
  682. DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize());
  683. }
  684. void assignFrom(PrivateData* const other)
  685. {
  686. imageNormal = other->imageNormal;
  687. imageDown = other->imageDown;
  688. isDown = other->isDown;
  689. callback = other->callback;
  690. DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize());
  691. }
  692. DISTRHO_DECLARE_NON_COPYABLE(PrivateData)
  693. };
  694. // --------------------------------------------------------------------------------------------------------------------
  695. template <class ImageType>
  696. ImageBaseSwitch<ImageType>::ImageBaseSwitch(Widget* const parentWidget, const ImageType& imageNormal, const ImageType& imageDown) noexcept
  697. : SubWidget(parentWidget),
  698. pData(new PrivateData(imageNormal, imageDown))
  699. {
  700. setSize(imageNormal.getSize());
  701. }
  702. template <class ImageType>
  703. ImageBaseSwitch<ImageType>::ImageBaseSwitch(const ImageBaseSwitch<ImageType>& imageSwitch) noexcept
  704. : SubWidget(imageSwitch.getParentWidget()),
  705. pData(new PrivateData(imageSwitch.pData))
  706. {
  707. setSize(pData->imageNormal.getSize());
  708. }
  709. template <class ImageType>
  710. ImageBaseSwitch<ImageType>& ImageBaseSwitch<ImageType>::operator=(const ImageBaseSwitch<ImageType>& imageSwitch) noexcept
  711. {
  712. pData->assignFrom(imageSwitch.pData);
  713. setSize(pData->imageNormal.getSize());
  714. return *this;
  715. }
  716. template <class ImageType>
  717. ImageBaseSwitch<ImageType>::~ImageBaseSwitch()
  718. {
  719. delete pData;
  720. }
  721. template <class ImageType>
  722. bool ImageBaseSwitch<ImageType>::isDown() const noexcept
  723. {
  724. return pData->isDown;
  725. }
  726. template <class ImageType>
  727. void ImageBaseSwitch<ImageType>::setDown(const bool down) noexcept
  728. {
  729. if (pData->isDown == down)
  730. return;
  731. pData->isDown = down;
  732. repaint();
  733. }
  734. template <class ImageType>
  735. void ImageBaseSwitch<ImageType>::setCallback(Callback* const callback) noexcept
  736. {
  737. pData->callback = callback;
  738. }
  739. template <class ImageType>
  740. void ImageBaseSwitch<ImageType>::onDisplay()
  741. {
  742. const GraphicsContext& context(getGraphicsContext());
  743. if (pData->isDown)
  744. pData->imageDown.draw(context);
  745. else
  746. pData->imageNormal.draw(context);
  747. }
  748. template <class ImageType>
  749. bool ImageBaseSwitch<ImageType>::onMouse(const MouseEvent& ev)
  750. {
  751. if (ev.press && contains(ev.pos))
  752. {
  753. pData->isDown = !pData->isDown;
  754. repaint();
  755. if (pData->callback != nullptr)
  756. pData->callback->imageSwitchClicked(this, pData->isDown);
  757. return true;
  758. }
  759. return false;
  760. }
  761. // --------------------------------------------------------------------------------------------------------------------
  762. END_NAMESPACE_DGL