Audio plugin host https://kx.studio/carla
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.

1634 lines
59KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace juce
  20. {
  21. class Slider::Pimpl : public AsyncUpdater, // this needs to be public otherwise it will cause an
  22. // error when JUCE_DLL_BUILD=1
  23. private Button::Listener,
  24. private Label::Listener,
  25. private Value::Listener
  26. {
  27. public:
  28. Pimpl (Slider& s, SliderStyle sliderStyle, TextEntryBoxPosition textBoxPosition)
  29. : owner (s),
  30. style (sliderStyle),
  31. textBoxPos (textBoxPosition)
  32. {
  33. rotaryParams.startAngleRadians = float_Pi * 1.2f;
  34. rotaryParams.endAngleRadians = float_Pi * 2.8f;
  35. rotaryParams.stopAtEnd = true;
  36. }
  37. ~Pimpl()
  38. {
  39. currentValue.removeListener (this);
  40. valueMin.removeListener (this);
  41. valueMax.removeListener (this);
  42. popupDisplay = nullptr;
  43. }
  44. //==============================================================================
  45. void registerListeners()
  46. {
  47. currentValue.addListener (this);
  48. valueMin.addListener (this);
  49. valueMax.addListener (this);
  50. }
  51. bool isHorizontal() const noexcept
  52. {
  53. return style == LinearHorizontal
  54. || style == LinearBar
  55. || style == TwoValueHorizontal
  56. || style == ThreeValueHorizontal;
  57. }
  58. bool isVertical() const noexcept
  59. {
  60. return style == LinearVertical
  61. || style == LinearBarVertical
  62. || style == TwoValueVertical
  63. || style == ThreeValueVertical;
  64. }
  65. bool isRotary() const noexcept
  66. {
  67. return style == Rotary
  68. || style == RotaryHorizontalDrag
  69. || style == RotaryVerticalDrag
  70. || style == RotaryHorizontalVerticalDrag;
  71. }
  72. bool isBar() const noexcept
  73. {
  74. return style == LinearBar
  75. || style == LinearBarVertical;
  76. }
  77. bool incDecDragDirectionIsHorizontal() const noexcept
  78. {
  79. return incDecButtonMode == incDecButtonsDraggable_Horizontal
  80. || (incDecButtonMode == incDecButtonsDraggable_AutoDirection && incDecButtonsSideBySide);
  81. }
  82. float getPositionOfValue (const double value) const
  83. {
  84. if (isHorizontal() || isVertical())
  85. return getLinearSliderPos (value);
  86. jassertfalse; // not a valid call on a slider that doesn't work linearly!
  87. return 0.0f;
  88. }
  89. void setRange (const double newMin, const double newMax, const double newInt)
  90. {
  91. if (minimum != newMin || maximum != newMax || interval != newInt)
  92. {
  93. minimum = newMin;
  94. maximum = newMax;
  95. interval = newInt;
  96. // figure out the number of DPs needed to display all values at this
  97. // interval setting.
  98. numDecimalPlaces = 7;
  99. if (newInt != 0.0)
  100. {
  101. int v = std::abs (roundToInt (newInt * 10000000));
  102. if (v > 0)
  103. {
  104. while ((v % 10) == 0)
  105. {
  106. --numDecimalPlaces;
  107. v /= 10;
  108. }
  109. }
  110. }
  111. // keep the current values inside the new range..
  112. if (style != TwoValueHorizontal && style != TwoValueVertical)
  113. {
  114. setValue (getValue(), dontSendNotification);
  115. }
  116. else
  117. {
  118. setMinValue (getMinValue(), dontSendNotification, false);
  119. setMaxValue (getMaxValue(), dontSendNotification, false);
  120. }
  121. updateText();
  122. }
  123. }
  124. double getValue() const
  125. {
  126. // for a two-value style slider, you should use the getMinValue() and getMaxValue()
  127. // methods to get the two values.
  128. jassert (style != TwoValueHorizontal && style != TwoValueVertical);
  129. return currentValue.getValue();
  130. }
  131. void setValue (double newValue, const NotificationType notification)
  132. {
  133. // for a two-value style slider, you should use the setMinValue() and setMaxValue()
  134. // methods to set the two values.
  135. jassert (style != TwoValueHorizontal && style != TwoValueVertical);
  136. newValue = constrainedValue (newValue);
  137. if (style == ThreeValueHorizontal || style == ThreeValueVertical)
  138. {
  139. jassert (static_cast<double> (valueMin.getValue()) <= static_cast<double> (valueMax.getValue()));
  140. newValue = jlimit (static_cast<double> (valueMin.getValue()),
  141. static_cast<double> (valueMax.getValue()),
  142. newValue);
  143. }
  144. if (newValue != lastCurrentValue)
  145. {
  146. if (valueBox != nullptr)
  147. valueBox->hideEditor (true);
  148. lastCurrentValue = newValue;
  149. // (need to do this comparison because the Value will use equalsWithSameType to compare
  150. // the new and old values, so will generate unwanted change events if the type changes)
  151. if (currentValue != newValue)
  152. currentValue = newValue;
  153. updateText();
  154. owner.repaint();
  155. updatePopupDisplay (newValue);
  156. triggerChangeMessage (notification);
  157. }
  158. }
  159. void setMinValue (double newValue, const NotificationType notification,
  160. const bool allowNudgingOfOtherValues)
  161. {
  162. // The minimum value only applies to sliders that are in two- or three-value mode.
  163. jassert (style == TwoValueHorizontal || style == TwoValueVertical
  164. || style == ThreeValueHorizontal || style == ThreeValueVertical);
  165. newValue = constrainedValue (newValue);
  166. if (style == TwoValueHorizontal || style == TwoValueVertical)
  167. {
  168. if (allowNudgingOfOtherValues && newValue > static_cast<double> (valueMax.getValue()))
  169. setMaxValue (newValue, notification, false);
  170. newValue = jmin (static_cast<double> (valueMax.getValue()), newValue);
  171. }
  172. else
  173. {
  174. if (allowNudgingOfOtherValues && newValue > lastCurrentValue)
  175. setValue (newValue, notification);
  176. newValue = jmin (lastCurrentValue, newValue);
  177. }
  178. if (lastValueMin != newValue)
  179. {
  180. lastValueMin = newValue;
  181. valueMin = newValue;
  182. owner.repaint();
  183. updatePopupDisplay (newValue);
  184. triggerChangeMessage (notification);
  185. }
  186. }
  187. void setMaxValue (double newValue, const NotificationType notification,
  188. const bool allowNudgingOfOtherValues)
  189. {
  190. // The maximum value only applies to sliders that are in two- or three-value mode.
  191. jassert (style == TwoValueHorizontal || style == TwoValueVertical
  192. || style == ThreeValueHorizontal || style == ThreeValueVertical);
  193. newValue = constrainedValue (newValue);
  194. if (style == TwoValueHorizontal || style == TwoValueVertical)
  195. {
  196. if (allowNudgingOfOtherValues && newValue < static_cast<double> (valueMin.getValue()))
  197. setMinValue (newValue, notification, false);
  198. newValue = jmax (static_cast<double> (valueMin.getValue()), newValue);
  199. }
  200. else
  201. {
  202. if (allowNudgingOfOtherValues && newValue < lastCurrentValue)
  203. setValue (newValue, notification);
  204. newValue = jmax (lastCurrentValue, newValue);
  205. }
  206. if (lastValueMax != newValue)
  207. {
  208. lastValueMax = newValue;
  209. valueMax = newValue;
  210. owner.repaint();
  211. updatePopupDisplay (valueMax.getValue());
  212. triggerChangeMessage (notification);
  213. }
  214. }
  215. void setMinAndMaxValues (double newMinValue, double newMaxValue, const NotificationType notification)
  216. {
  217. // The maximum value only applies to sliders that are in two- or three-value mode.
  218. jassert (style == TwoValueHorizontal || style == TwoValueVertical
  219. || style == ThreeValueHorizontal || style == ThreeValueVertical);
  220. if (newMaxValue < newMinValue)
  221. std::swap (newMaxValue, newMinValue);
  222. newMinValue = constrainedValue (newMinValue);
  223. newMaxValue = constrainedValue (newMaxValue);
  224. if (lastValueMax != newMaxValue || lastValueMin != newMinValue)
  225. {
  226. lastValueMax = newMaxValue;
  227. lastValueMin = newMinValue;
  228. valueMin = newMinValue;
  229. valueMax = newMaxValue;
  230. owner.repaint();
  231. triggerChangeMessage (notification);
  232. }
  233. }
  234. double getMinValue() const
  235. {
  236. // The minimum value only applies to sliders that are in two- or three-value mode.
  237. jassert (style == TwoValueHorizontal || style == TwoValueVertical
  238. || style == ThreeValueHorizontal || style == ThreeValueVertical);
  239. return valueMin.getValue();
  240. }
  241. double getMaxValue() const
  242. {
  243. // The maximum value only applies to sliders that are in two- or three-value mode.
  244. jassert (style == TwoValueHorizontal || style == TwoValueVertical
  245. || style == ThreeValueHorizontal || style == ThreeValueVertical);
  246. return valueMax.getValue();
  247. }
  248. void triggerChangeMessage (const NotificationType notification)
  249. {
  250. if (notification != dontSendNotification)
  251. {
  252. owner.valueChanged();
  253. if (notification == sendNotificationSync)
  254. handleAsyncUpdate();
  255. else
  256. triggerAsyncUpdate();
  257. }
  258. }
  259. void handleAsyncUpdate() override
  260. {
  261. cancelPendingUpdate();
  262. Component::BailOutChecker checker (&owner);
  263. Slider* slider = &owner; // (must use an intermediate variable here to avoid a VS2005 compiler bug)
  264. listeners.callChecked (checker, &Slider::Listener::sliderValueChanged, slider);
  265. }
  266. void sendDragStart()
  267. {
  268. owner.startedDragging();
  269. Component::BailOutChecker checker (&owner);
  270. Slider* slider = &owner; // (must use an intermediate variable here to avoid a VS2005 compiler bug)
  271. listeners.callChecked (checker, &Slider::Listener::sliderDragStarted, slider);
  272. }
  273. void sendDragEnd()
  274. {
  275. owner.stoppedDragging();
  276. sliderBeingDragged = -1;
  277. Component::BailOutChecker checker (&owner);
  278. Slider* slider = &owner; // (must use an intermediate variable here to avoid a VS2005 compiler bug)
  279. listeners.callChecked (checker, &Slider::Listener::sliderDragEnded, slider);
  280. }
  281. struct DragInProgress
  282. {
  283. DragInProgress (Pimpl& p) : owner (p) { owner.sendDragStart(); }
  284. ~DragInProgress() { owner.sendDragEnd(); }
  285. Pimpl& owner;
  286. JUCE_DECLARE_NON_COPYABLE (DragInProgress)
  287. };
  288. void buttonClicked (Button* button) override
  289. {
  290. if (style == IncDecButtons)
  291. {
  292. const double delta = (button == incButton) ? interval : -interval;
  293. auto newValue = owner.snapValue (getValue() + delta, notDragging);
  294. if (currentDrag != nullptr)
  295. {
  296. setValue (newValue, sendNotificationSync);
  297. }
  298. else
  299. {
  300. DragInProgress drag (*this);
  301. setValue (newValue, sendNotificationSync);
  302. }
  303. }
  304. }
  305. void valueChanged (Value& value) override
  306. {
  307. if (value.refersToSameSourceAs (currentValue))
  308. {
  309. if (style != TwoValueHorizontal && style != TwoValueVertical)
  310. setValue (currentValue.getValue(), dontSendNotification);
  311. }
  312. else if (value.refersToSameSourceAs (valueMin))
  313. setMinValue (valueMin.getValue(), dontSendNotification, true);
  314. else if (value.refersToSameSourceAs (valueMax))
  315. setMaxValue (valueMax.getValue(), dontSendNotification, true);
  316. }
  317. void labelTextChanged (Label* label) override
  318. {
  319. const double newValue = owner.snapValue (owner.getValueFromText (label->getText()), notDragging);
  320. if (newValue != static_cast<double> (currentValue.getValue()))
  321. {
  322. DragInProgress drag (*this);
  323. setValue (newValue, sendNotificationSync);
  324. }
  325. updateText(); // force a clean-up of the text, needed in case setValue() hasn't done this.
  326. }
  327. void updateText()
  328. {
  329. if (valueBox != nullptr)
  330. {
  331. String newValue (owner.getTextFromValue (currentValue.getValue()));
  332. if (newValue != valueBox->getText())
  333. valueBox->setText (newValue, dontSendNotification);
  334. }
  335. }
  336. double constrainedValue (double value) const
  337. {
  338. if (interval > 0)
  339. value = minimum + interval * std::floor ((value - minimum) / interval + 0.5);
  340. if (value <= minimum || maximum <= minimum)
  341. value = minimum;
  342. else if (value >= maximum)
  343. value = maximum;
  344. return value;
  345. }
  346. float getLinearSliderPos (const double value) const
  347. {
  348. double pos;
  349. if (maximum <= minimum)
  350. pos = 0.5;
  351. else if (value < minimum)
  352. pos = 0.0;
  353. else if (value > maximum)
  354. pos = 1.0;
  355. else
  356. pos = owner.valueToProportionOfLength (value);
  357. if (isVertical() || style == IncDecButtons)
  358. pos = 1.0 - pos;
  359. jassert (pos >= 0 && pos <= 1.0);
  360. return (float) (sliderRegionStart + pos * sliderRegionSize);
  361. }
  362. void setSliderStyle (const SliderStyle newStyle)
  363. {
  364. if (style != newStyle)
  365. {
  366. style = newStyle;
  367. owner.repaint();
  368. owner.lookAndFeelChanged();
  369. }
  370. }
  371. void setVelocityModeParameters (const double sensitivity, const int threshold,
  372. const double offset, const bool userCanPressKeyToSwapMode)
  373. {
  374. velocityModeSensitivity = sensitivity;
  375. velocityModeOffset = offset;
  376. velocityModeThreshold = threshold;
  377. userKeyOverridesVelocity = userCanPressKeyToSwapMode;
  378. }
  379. void setSkewFactorFromMidPoint (const double sliderValueToShowAtMidPoint)
  380. {
  381. if (maximum > minimum)
  382. skewFactor = std::log (0.5) / std::log ((sliderValueToShowAtMidPoint - minimum)
  383. / (maximum - minimum));
  384. }
  385. void setIncDecButtonsMode (const IncDecButtonMode mode)
  386. {
  387. if (incDecButtonMode != mode)
  388. {
  389. incDecButtonMode = mode;
  390. owner.lookAndFeelChanged();
  391. }
  392. }
  393. void setTextBoxStyle (const TextEntryBoxPosition newPosition,
  394. const bool isReadOnly,
  395. const int textEntryBoxWidth,
  396. const int textEntryBoxHeight)
  397. {
  398. if (textBoxPos != newPosition
  399. || editableText != (! isReadOnly)
  400. || textBoxWidth != textEntryBoxWidth
  401. || textBoxHeight != textEntryBoxHeight)
  402. {
  403. textBoxPos = newPosition;
  404. editableText = ! isReadOnly;
  405. textBoxWidth = textEntryBoxWidth;
  406. textBoxHeight = textEntryBoxHeight;
  407. owner.repaint();
  408. owner.lookAndFeelChanged();
  409. }
  410. }
  411. void setTextBoxIsEditable (const bool shouldBeEditable)
  412. {
  413. editableText = shouldBeEditable;
  414. updateTextBoxEnablement();
  415. }
  416. void showTextBox()
  417. {
  418. jassert (editableText); // this should probably be avoided in read-only sliders.
  419. if (valueBox != nullptr)
  420. valueBox->showEditor();
  421. }
  422. void hideTextBox (const bool discardCurrentEditorContents)
  423. {
  424. if (valueBox != nullptr)
  425. {
  426. valueBox->hideEditor (discardCurrentEditorContents);
  427. if (discardCurrentEditorContents)
  428. updateText();
  429. }
  430. }
  431. void setTextValueSuffix (const String& suffix)
  432. {
  433. if (textSuffix != suffix)
  434. {
  435. textSuffix = suffix;
  436. updateText();
  437. }
  438. }
  439. void updateTextBoxEnablement()
  440. {
  441. if (valueBox != nullptr)
  442. {
  443. const bool shouldBeEditable = editableText && owner.isEnabled();
  444. if (valueBox->isEditable() != shouldBeEditable) // (to avoid changing the single/double click flags unless we need to)
  445. valueBox->setEditable (shouldBeEditable);
  446. }
  447. }
  448. void lookAndFeelChanged (LookAndFeel& lf)
  449. {
  450. if (textBoxPos != NoTextBox)
  451. {
  452. const String previousTextBoxContent (valueBox != nullptr ? valueBox->getText()
  453. : owner.getTextFromValue (currentValue.getValue()));
  454. valueBox = nullptr;
  455. owner.addAndMakeVisible (valueBox = lf.createSliderTextBox (owner));
  456. valueBox->setWantsKeyboardFocus (false);
  457. valueBox->setText (previousTextBoxContent, dontSendNotification);
  458. valueBox->setTooltip (owner.getTooltip());
  459. updateTextBoxEnablement();
  460. valueBox->addListener (this);
  461. if (style == LinearBar || style == LinearBarVertical)
  462. {
  463. valueBox->addMouseListener (&owner, false);
  464. valueBox->setMouseCursor (MouseCursor::ParentCursor);
  465. }
  466. }
  467. else
  468. {
  469. valueBox = nullptr;
  470. }
  471. if (style == IncDecButtons)
  472. {
  473. owner.addAndMakeVisible (incButton = lf.createSliderButton (owner, true));
  474. incButton->addListener (this);
  475. owner.addAndMakeVisible (decButton = lf.createSliderButton (owner, false));
  476. decButton->addListener (this);
  477. if (incDecButtonMode != incDecButtonsNotDraggable)
  478. {
  479. incButton->addMouseListener (&owner, false);
  480. decButton->addMouseListener (&owner, false);
  481. }
  482. else
  483. {
  484. incButton->setRepeatSpeed (300, 100, 20);
  485. decButton->setRepeatSpeed (300, 100, 20);
  486. }
  487. const String tooltip (owner.getTooltip());
  488. incButton->setTooltip (tooltip);
  489. decButton->setTooltip (tooltip);
  490. }
  491. else
  492. {
  493. incButton = nullptr;
  494. decButton = nullptr;
  495. }
  496. owner.setComponentEffect (lf.getSliderEffect (owner));
  497. owner.resized();
  498. owner.repaint();
  499. }
  500. void showPopupMenu()
  501. {
  502. PopupMenu m;
  503. m.setLookAndFeel (&owner.getLookAndFeel());
  504. m.addItem (1, TRANS ("Velocity-sensitive mode"), true, isVelocityBased);
  505. m.addSeparator();
  506. if (isRotary())
  507. {
  508. PopupMenu rotaryMenu;
  509. rotaryMenu.addItem (2, TRANS ("Use circular dragging"), true, style == Rotary);
  510. rotaryMenu.addItem (3, TRANS ("Use left-right dragging"), true, style == RotaryHorizontalDrag);
  511. rotaryMenu.addItem (4, TRANS ("Use up-down dragging"), true, style == RotaryVerticalDrag);
  512. rotaryMenu.addItem (5, TRANS ("Use left-right/up-down dragging"), true, style == RotaryHorizontalVerticalDrag);
  513. m.addSubMenu (TRANS ("Rotary mode"), rotaryMenu);
  514. }
  515. m.showMenuAsync (PopupMenu::Options(),
  516. ModalCallbackFunction::forComponent (sliderMenuCallback, &owner));
  517. }
  518. static void sliderMenuCallback (const int result, Slider* slider)
  519. {
  520. if (slider != nullptr)
  521. {
  522. switch (result)
  523. {
  524. case 1: slider->setVelocityBasedMode (! slider->getVelocityBasedMode()); break;
  525. case 2: slider->setSliderStyle (Rotary); break;
  526. case 3: slider->setSliderStyle (RotaryHorizontalDrag); break;
  527. case 4: slider->setSliderStyle (RotaryVerticalDrag); break;
  528. case 5: slider->setSliderStyle (RotaryHorizontalVerticalDrag); break;
  529. default: break;
  530. }
  531. }
  532. }
  533. int getThumbIndexAt (const MouseEvent& e)
  534. {
  535. const bool isTwoValue = (style == TwoValueHorizontal || style == TwoValueVertical);
  536. const bool isThreeValue = (style == ThreeValueHorizontal || style == ThreeValueVertical);
  537. if (isTwoValue || isThreeValue)
  538. {
  539. const float mousePos = isVertical() ? e.position.y : e.position.x;
  540. const float normalPosDistance = std::abs (getLinearSliderPos (currentValue.getValue()) - mousePos);
  541. const float minPosDistance = std::abs (getLinearSliderPos (valueMin.getValue()) + (isVertical() ? 0.1f : -0.1f) - mousePos);
  542. const float maxPosDistance = std::abs (getLinearSliderPos (valueMax.getValue()) + (isVertical() ? -0.1f : 0.1f) - mousePos);
  543. if (isTwoValue)
  544. return maxPosDistance <= minPosDistance ? 2 : 1;
  545. if (normalPosDistance >= minPosDistance && maxPosDistance >= minPosDistance)
  546. return 1;
  547. if (normalPosDistance >= maxPosDistance)
  548. return 2;
  549. }
  550. return 0;
  551. }
  552. //==============================================================================
  553. void handleRotaryDrag (const MouseEvent& e)
  554. {
  555. const float dx = e.position.x - sliderRect.getCentreX();
  556. const float dy = e.position.y - sliderRect.getCentreY();
  557. if (dx * dx + dy * dy > 25.0f)
  558. {
  559. double angle = std::atan2 ((double) dx, (double) -dy);
  560. while (angle < 0.0)
  561. angle += double_Pi * 2.0;
  562. if (rotaryParams.stopAtEnd && e.mouseWasDraggedSinceMouseDown())
  563. {
  564. if (std::abs (angle - lastAngle) > double_Pi)
  565. {
  566. if (angle >= lastAngle)
  567. angle -= double_Pi * 2.0;
  568. else
  569. angle += double_Pi * 2.0;
  570. }
  571. if (angle >= lastAngle)
  572. angle = jmin (angle, (double) jmax (rotaryParams.startAngleRadians, rotaryParams.endAngleRadians));
  573. else
  574. angle = jmax (angle, (double) jmin (rotaryParams.startAngleRadians, rotaryParams.endAngleRadians));
  575. }
  576. else
  577. {
  578. while (angle < rotaryParams.startAngleRadians)
  579. angle += double_Pi * 2.0;
  580. if (angle > rotaryParams.endAngleRadians)
  581. {
  582. if (smallestAngleBetween (angle, rotaryParams.startAngleRadians)
  583. <= smallestAngleBetween (angle, rotaryParams.endAngleRadians))
  584. angle = rotaryParams.startAngleRadians;
  585. else
  586. angle = rotaryParams.endAngleRadians;
  587. }
  588. }
  589. const double proportion = (angle - rotaryParams.startAngleRadians) / (rotaryParams.endAngleRadians - rotaryParams.startAngleRadians);
  590. valueWhenLastDragged = owner.proportionOfLengthToValue (jlimit (0.0, 1.0, proportion));
  591. lastAngle = angle;
  592. }
  593. }
  594. void handleAbsoluteDrag (const MouseEvent& e)
  595. {
  596. const float mousePos = (isHorizontal() || style == RotaryHorizontalDrag) ? e.position.x : e.position.y;
  597. double newPos = 0;
  598. if (style == RotaryHorizontalDrag
  599. || style == RotaryVerticalDrag
  600. || style == IncDecButtons
  601. || ((style == LinearHorizontal || style == LinearVertical || style == LinearBar || style == LinearBarVertical)
  602. && ! snapsToMousePos))
  603. {
  604. auto mouseDiff = (style == RotaryHorizontalDrag
  605. || style == LinearHorizontal
  606. || style == LinearBar
  607. || (style == IncDecButtons && incDecDragDirectionIsHorizontal()))
  608. ? e.position.x - mouseDragStartPos.x
  609. : mouseDragStartPos.y - e.position.y;
  610. newPos = owner.valueToProportionOfLength (valueOnMouseDown)
  611. + mouseDiff * (1.0 / pixelsForFullDragExtent);
  612. if (style == IncDecButtons)
  613. {
  614. incButton->setState (mouseDiff < 0 ? Button::buttonNormal : Button::buttonDown);
  615. decButton->setState (mouseDiff > 0 ? Button::buttonNormal : Button::buttonDown);
  616. }
  617. }
  618. else if (style == RotaryHorizontalVerticalDrag)
  619. {
  620. auto mouseDiff = (e.position.x - mouseDragStartPos.x)
  621. + (mouseDragStartPos.y - e.position.y);
  622. newPos = owner.valueToProportionOfLength (valueOnMouseDown)
  623. + mouseDiff * (1.0 / pixelsForFullDragExtent);
  624. }
  625. else
  626. {
  627. newPos = (mousePos - sliderRegionStart) / (double) sliderRegionSize;
  628. if (isVertical())
  629. newPos = 1.0 - newPos;
  630. }
  631. valueWhenLastDragged = owner.proportionOfLengthToValue (jlimit (0.0, 1.0, newPos));
  632. }
  633. void handleVelocityDrag (const MouseEvent& e)
  634. {
  635. const bool hasHorizontalStyle =
  636. (isHorizontal() || style == RotaryHorizontalDrag
  637. || (style == IncDecButtons && incDecDragDirectionIsHorizontal()));
  638. float mouseDiff;
  639. if (style == RotaryHorizontalVerticalDrag)
  640. mouseDiff = (e.position.x - mousePosWhenLastDragged.x) + (mousePosWhenLastDragged.y - e.position.y);
  641. else
  642. mouseDiff = (hasHorizontalStyle ? e.position.x - mousePosWhenLastDragged.x
  643. : e.position.y - mousePosWhenLastDragged.y);
  644. const double maxSpeed = jmax (200, sliderRegionSize);
  645. double speed = jlimit (0.0, maxSpeed, (double) std::abs (mouseDiff));
  646. if (speed != 0.0)
  647. {
  648. speed = 0.2 * velocityModeSensitivity
  649. * (1.0 + std::sin (double_Pi * (1.5 + jmin (0.5, velocityModeOffset
  650. + jmax (0.0, (double) (speed - velocityModeThreshold))
  651. / maxSpeed))));
  652. if (mouseDiff < 0)
  653. speed = -speed;
  654. if (isVertical() || style == RotaryVerticalDrag
  655. || (style == IncDecButtons && ! incDecDragDirectionIsHorizontal()))
  656. speed = -speed;
  657. const double currentPos = owner.valueToProportionOfLength (valueWhenLastDragged);
  658. valueWhenLastDragged = owner.proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + speed));
  659. e.source.enableUnboundedMouseMovement (true, false);
  660. }
  661. }
  662. void mouseDown (const MouseEvent& e)
  663. {
  664. incDecDragged = false;
  665. useDragEvents = false;
  666. mouseDragStartPos = mousePosWhenLastDragged = e.position;
  667. currentDrag = nullptr;
  668. popupDisplay = nullptr;
  669. if (owner.isEnabled())
  670. {
  671. if (e.mods.isPopupMenu() && menuEnabled)
  672. {
  673. showPopupMenu();
  674. }
  675. else if (canDoubleClickToValue()
  676. && e.mods.withoutMouseButtons() == ModifierKeys (ModifierKeys::altModifier))
  677. {
  678. mouseDoubleClick();
  679. }
  680. else if (maximum > minimum)
  681. {
  682. useDragEvents = true;
  683. if (valueBox != nullptr)
  684. valueBox->hideEditor (true);
  685. sliderBeingDragged = getThumbIndexAt (e);
  686. minMaxDiff = static_cast<double> (valueMax.getValue()) - static_cast<double> (valueMin.getValue());
  687. lastAngle = rotaryParams.startAngleRadians
  688. + (rotaryParams.endAngleRadians - rotaryParams.startAngleRadians)
  689. * owner.valueToProportionOfLength (currentValue.getValue());
  690. valueWhenLastDragged = (sliderBeingDragged == 2 ? valueMax
  691. : (sliderBeingDragged == 1 ? valueMin
  692. : currentValue)).getValue();
  693. valueOnMouseDown = valueWhenLastDragged;
  694. if (showPopupOnDrag || showPopupOnHover)
  695. {
  696. showPopupDisplay();
  697. popupDisplay->stopTimer();
  698. }
  699. currentDrag = new DragInProgress (*this);
  700. mouseDrag (e);
  701. }
  702. }
  703. }
  704. void mouseDrag (const MouseEvent& e)
  705. {
  706. if (useDragEvents && maximum > minimum
  707. && ! ((style == LinearBar || style == LinearBarVertical)
  708. && e.mouseWasClicked() && valueBox != nullptr && valueBox->isEditable()))
  709. {
  710. DragMode dragMode = notDragging;
  711. if (style == Rotary)
  712. {
  713. handleRotaryDrag (e);
  714. }
  715. else
  716. {
  717. if (style == IncDecButtons && ! incDecDragged)
  718. {
  719. if (e.getDistanceFromDragStart() < 10 || ! e.mouseWasDraggedSinceMouseDown())
  720. return;
  721. incDecDragged = true;
  722. mouseDragStartPos = e.position;
  723. }
  724. if (isAbsoluteDragMode (e.mods) || (maximum - minimum) / sliderRegionSize < interval)
  725. {
  726. dragMode = absoluteDrag;
  727. handleAbsoluteDrag (e);
  728. }
  729. else
  730. {
  731. dragMode = velocityDrag;
  732. handleVelocityDrag (e);
  733. }
  734. }
  735. valueWhenLastDragged = jlimit (minimum, maximum, valueWhenLastDragged);
  736. if (sliderBeingDragged == 0)
  737. {
  738. setValue (owner.snapValue (valueWhenLastDragged, dragMode),
  739. sendChangeOnlyOnRelease ? dontSendNotification : sendNotificationSync);
  740. }
  741. else if (sliderBeingDragged == 1)
  742. {
  743. setMinValue (owner.snapValue (valueWhenLastDragged, dragMode),
  744. sendChangeOnlyOnRelease ? dontSendNotification : sendNotificationAsync, true);
  745. if (e.mods.isShiftDown())
  746. setMaxValue (getMinValue() + minMaxDiff, dontSendNotification, true);
  747. else
  748. minMaxDiff = static_cast<double> (valueMax.getValue()) - static_cast<double> (valueMin.getValue());
  749. }
  750. else if (sliderBeingDragged == 2)
  751. {
  752. setMaxValue (owner.snapValue (valueWhenLastDragged, dragMode),
  753. sendChangeOnlyOnRelease ? dontSendNotification : sendNotificationAsync, true);
  754. if (e.mods.isShiftDown())
  755. setMinValue (getMaxValue() - minMaxDiff, dontSendNotification, true);
  756. else
  757. minMaxDiff = static_cast<double> (valueMax.getValue()) - static_cast<double> (valueMin.getValue());
  758. }
  759. mousePosWhenLastDragged = e.position;
  760. }
  761. }
  762. void mouseUp()
  763. {
  764. if (owner.isEnabled()
  765. && useDragEvents
  766. && (maximum > minimum)
  767. && (style != IncDecButtons || incDecDragged))
  768. {
  769. restoreMouseIfHidden();
  770. if (sendChangeOnlyOnRelease && valueOnMouseDown != static_cast<double> (currentValue.getValue()))
  771. triggerChangeMessage (sendNotificationAsync);
  772. currentDrag = nullptr;
  773. popupDisplay = nullptr;
  774. if (style == IncDecButtons)
  775. {
  776. incButton->setState (Button::buttonNormal);
  777. decButton->setState (Button::buttonNormal);
  778. }
  779. }
  780. else if (popupDisplay != nullptr)
  781. {
  782. popupDisplay->startTimer (200);
  783. }
  784. currentDrag = nullptr;
  785. }
  786. void mouseMove()
  787. {
  788. if (showPopupOnHover
  789. && style != TwoValueHorizontal
  790. && style != TwoValueVertical)
  791. {
  792. if (owner.isMouseOver (true))
  793. {
  794. if (popupDisplay == nullptr)
  795. showPopupDisplay();
  796. if (popupDisplay != nullptr)
  797. popupDisplay->startTimer (2000);
  798. }
  799. }
  800. }
  801. void mouseExit()
  802. {
  803. popupDisplay = nullptr;
  804. }
  805. void showPopupDisplay()
  806. {
  807. if (popupDisplay == nullptr)
  808. {
  809. popupDisplay = new PopupDisplayComponent (owner);
  810. updatePopupDisplay (getValue());
  811. if (parentForPopupDisplay != nullptr)
  812. parentForPopupDisplay->addChildComponent (popupDisplay);
  813. else
  814. popupDisplay->addToDesktop (ComponentPeer::windowIsTemporary);
  815. popupDisplay->setVisible (true);
  816. }
  817. }
  818. void updatePopupDisplay (double valueToShow)
  819. {
  820. if (popupDisplay != nullptr)
  821. popupDisplay->updatePosition (owner.getTextFromValue (valueToShow));
  822. }
  823. bool canDoubleClickToValue() const
  824. {
  825. return doubleClickToValue
  826. && style != IncDecButtons
  827. && minimum <= doubleClickReturnValue
  828. && maximum >= doubleClickReturnValue;
  829. }
  830. void mouseDoubleClick()
  831. {
  832. if (canDoubleClickToValue())
  833. {
  834. DragInProgress drag (*this);
  835. setValue (doubleClickReturnValue, sendNotificationSync);
  836. }
  837. }
  838. double getMouseWheelDelta (double value, double wheelAmount)
  839. {
  840. if (style == IncDecButtons)
  841. return interval * wheelAmount;
  842. const double proportionDelta = wheelAmount * 0.15f;
  843. const double currentPos = owner.valueToProportionOfLength (value);
  844. return owner.proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + proportionDelta)) - value;
  845. }
  846. bool mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
  847. {
  848. if (scrollWheelEnabled
  849. && style != TwoValueHorizontal
  850. && style != TwoValueVertical)
  851. {
  852. // sometimes duplicate wheel events seem to be sent, so since we're going to
  853. // bump the value by a minimum of the interval, avoid doing this twice..
  854. if (e.eventTime != lastMouseWheelTime)
  855. {
  856. lastMouseWheelTime = e.eventTime;
  857. if (maximum > minimum && ! e.mods.isAnyMouseButtonDown())
  858. {
  859. if (valueBox != nullptr)
  860. valueBox->hideEditor (false);
  861. const double value = static_cast<double> (currentValue.getValue());
  862. const double delta = getMouseWheelDelta (value, (std::abs (wheel.deltaX) > std::abs (wheel.deltaY)
  863. ? -wheel.deltaX : wheel.deltaY)
  864. * (wheel.isReversed ? -1.0f : 1.0f));
  865. if (delta != 0.0)
  866. {
  867. const double newValue = value + jmax (interval, std::abs (delta)) * (delta < 0 ? -1.0 : 1.0);
  868. DragInProgress drag (*this);
  869. setValue (owner.snapValue (newValue, notDragging), sendNotificationSync);
  870. }
  871. }
  872. }
  873. return true;
  874. }
  875. return false;
  876. }
  877. void modifierKeysChanged (const ModifierKeys& modifiers)
  878. {
  879. if (style != IncDecButtons && style != Rotary && isAbsoluteDragMode (modifiers))
  880. restoreMouseIfHidden();
  881. }
  882. bool isAbsoluteDragMode (ModifierKeys mods) const
  883. {
  884. return isVelocityBased == (userKeyOverridesVelocity
  885. && mods.testFlags (ModifierKeys::ctrlAltCommandModifiers));
  886. }
  887. void restoreMouseIfHidden()
  888. {
  889. for (auto& ms : Desktop::getInstance().getMouseSources())
  890. {
  891. if (ms.isUnboundedMouseMovementEnabled())
  892. {
  893. ms.enableUnboundedMouseMovement (false);
  894. const double pos = sliderBeingDragged == 2 ? getMaxValue()
  895. : (sliderBeingDragged == 1 ? getMinValue()
  896. : static_cast<double> (currentValue.getValue()));
  897. Point<float> mousePos;
  898. if (isRotary())
  899. {
  900. mousePos = ms.getLastMouseDownPosition();
  901. const float delta = (float) (pixelsForFullDragExtent * (owner.valueToProportionOfLength (valueOnMouseDown)
  902. - owner.valueToProportionOfLength (pos)));
  903. if (style == RotaryHorizontalDrag) mousePos += Point<float> (-delta, 0.0f);
  904. else if (style == RotaryVerticalDrag) mousePos += Point<float> (0.0f, delta);
  905. else mousePos += Point<float> (delta / -2.0f, delta / 2.0f);
  906. mousePos = owner.getScreenBounds().reduced (4).toFloat().getConstrainedPoint (mousePos);
  907. mouseDragStartPos = mousePosWhenLastDragged = owner.getLocalPoint (nullptr, mousePos);
  908. valueOnMouseDown = valueWhenLastDragged;
  909. }
  910. else
  911. {
  912. const float pixelPos = (float) getLinearSliderPos (pos);
  913. mousePos = owner.localPointToGlobal (Point<float> (isHorizontal() ? pixelPos : (owner.getWidth() / 2.0f),
  914. isVertical() ? pixelPos : (owner.getHeight() / 2.0f)));
  915. }
  916. ms.setScreenPosition (mousePos);
  917. }
  918. }
  919. }
  920. //==============================================================================
  921. void paint (Graphics& g, LookAndFeel& lf)
  922. {
  923. if (style != IncDecButtons)
  924. {
  925. if (isRotary())
  926. {
  927. const float sliderPos = (float) owner.valueToProportionOfLength (lastCurrentValue);
  928. jassert (sliderPos >= 0 && sliderPos <= 1.0f);
  929. lf.drawRotarySlider (g,
  930. sliderRect.getX(), sliderRect.getY(),
  931. sliderRect.getWidth(), sliderRect.getHeight(),
  932. sliderPos, rotaryParams.startAngleRadians,
  933. rotaryParams.endAngleRadians, owner);
  934. }
  935. else
  936. {
  937. lf.drawLinearSlider (g,
  938. sliderRect.getX(), sliderRect.getY(),
  939. sliderRect.getWidth(), sliderRect.getHeight(),
  940. getLinearSliderPos (lastCurrentValue),
  941. getLinearSliderPos (lastValueMin),
  942. getLinearSliderPos (lastValueMax),
  943. style, owner);
  944. }
  945. if ((style == LinearBar || style == LinearBarVertical) && valueBox == nullptr)
  946. {
  947. g.setColour (owner.findColour (Slider::textBoxOutlineColourId));
  948. g.drawRect (0, 0, owner.getWidth(), owner.getHeight(), 1);
  949. }
  950. }
  951. }
  952. //==============================================================================
  953. void resized (LookAndFeel& lf)
  954. {
  955. SliderLayout layout = lf.getSliderLayout (owner);
  956. sliderRect = layout.sliderBounds;
  957. if (valueBox != nullptr)
  958. valueBox->setBounds (layout.textBoxBounds);
  959. if (isHorizontal())
  960. {
  961. sliderRegionStart = layout.sliderBounds.getX();
  962. sliderRegionSize = layout.sliderBounds.getWidth();
  963. }
  964. else if (isVertical())
  965. {
  966. sliderRegionStart = layout.sliderBounds.getY();
  967. sliderRegionSize = layout.sliderBounds.getHeight();
  968. }
  969. else if (style == IncDecButtons)
  970. {
  971. resizeIncDecButtons();
  972. }
  973. }
  974. //==============================================================================
  975. void resizeIncDecButtons()
  976. {
  977. Rectangle<int> buttonRect (sliderRect);
  978. if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight)
  979. buttonRect.expand (-2, 0);
  980. else
  981. buttonRect.expand (0, -2);
  982. incDecButtonsSideBySide = buttonRect.getWidth() > buttonRect.getHeight();
  983. if (incDecButtonsSideBySide)
  984. {
  985. decButton->setBounds (buttonRect.removeFromLeft (buttonRect.getWidth() / 2));
  986. decButton->setConnectedEdges (Button::ConnectedOnRight);
  987. incButton->setConnectedEdges (Button::ConnectedOnLeft);
  988. }
  989. else
  990. {
  991. decButton->setBounds (buttonRect.removeFromBottom (buttonRect.getHeight() / 2));
  992. decButton->setConnectedEdges (Button::ConnectedOnTop);
  993. incButton->setConnectedEdges (Button::ConnectedOnBottom);
  994. }
  995. incButton->setBounds (buttonRect);
  996. }
  997. //==============================================================================
  998. Slider& owner;
  999. SliderStyle style;
  1000. ListenerList<Slider::Listener> listeners;
  1001. Value currentValue, valueMin, valueMax;
  1002. double lastCurrentValue = 0, lastValueMin = 0, lastValueMax = 0;
  1003. double minimum = 0, maximum = 10, interval = 0, doubleClickReturnValue = 0;
  1004. double valueWhenLastDragged = 0, valueOnMouseDown = 0, skewFactor = 1.0, lastAngle = 0;
  1005. bool symmetricSkew = false;
  1006. double velocityModeSensitivity = 1.0, velocityModeOffset = 0, minMaxDiff = 0;
  1007. int velocityModeThreshold = 1;
  1008. RotaryParameters rotaryParams;
  1009. Point<float> mouseDragStartPos, mousePosWhenLastDragged;
  1010. int sliderRegionStart = 0, sliderRegionSize = 1;
  1011. int sliderBeingDragged = -1;
  1012. int pixelsForFullDragExtent = 250;
  1013. Time lastMouseWheelTime;
  1014. Rectangle<int> sliderRect;
  1015. ScopedPointer<DragInProgress> currentDrag;
  1016. TextEntryBoxPosition textBoxPos;
  1017. String textSuffix;
  1018. int numDecimalPlaces = 7;
  1019. int textBoxWidth = 80, textBoxHeight = 20;
  1020. IncDecButtonMode incDecButtonMode = incDecButtonsNotDraggable;
  1021. bool editableText = true;
  1022. bool doubleClickToValue = false;
  1023. bool isVelocityBased = false;
  1024. bool userKeyOverridesVelocity = true;
  1025. bool incDecButtonsSideBySide = false;
  1026. bool sendChangeOnlyOnRelease = false;
  1027. bool showPopupOnDrag = false;
  1028. bool showPopupOnHover = false;
  1029. bool menuEnabled = false;
  1030. bool useDragEvents = false;
  1031. bool incDecDragged = false;
  1032. bool scrollWheelEnabled = true;
  1033. bool snapsToMousePos = true;
  1034. ScopedPointer<Label> valueBox;
  1035. ScopedPointer<Button> incButton, decButton;
  1036. //==============================================================================
  1037. struct PopupDisplayComponent : public BubbleComponent,
  1038. public Timer
  1039. {
  1040. PopupDisplayComponent (Slider& s)
  1041. : owner (s),
  1042. font (s.getLookAndFeel().getSliderPopupFont (s))
  1043. {
  1044. setAlwaysOnTop (true);
  1045. setAllowedPlacement (owner.getLookAndFeel().getSliderPopupPlacement (s));
  1046. setLookAndFeel (&s.getLookAndFeel());
  1047. }
  1048. void paintContent (Graphics& g, int w, int h) override
  1049. {
  1050. g.setFont (font);
  1051. g.setColour (owner.findColour (TooltipWindow::textColourId, true));
  1052. g.drawFittedText (text, Rectangle<int> (w, h), Justification::centred, 1);
  1053. }
  1054. void getContentSize (int& w, int& h) override
  1055. {
  1056. w = font.getStringWidth (text) + 18;
  1057. h = (int) (font.getHeight() * 1.6f);
  1058. }
  1059. void updatePosition (const String& newText)
  1060. {
  1061. text = newText;
  1062. BubbleComponent::setPosition (&owner);
  1063. repaint();
  1064. }
  1065. void timerCallback() override
  1066. {
  1067. owner.pimpl->popupDisplay = nullptr;
  1068. }
  1069. private:
  1070. Slider& owner;
  1071. Font font;
  1072. String text;
  1073. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PopupDisplayComponent)
  1074. };
  1075. ScopedPointer<PopupDisplayComponent> popupDisplay;
  1076. Component* parentForPopupDisplay = nullptr;
  1077. //==============================================================================
  1078. static double smallestAngleBetween (double a1, double a2) noexcept
  1079. {
  1080. return jmin (std::abs (a1 - a2),
  1081. std::abs (a1 + double_Pi * 2.0 - a2),
  1082. std::abs (a2 + double_Pi * 2.0 - a1));
  1083. }
  1084. };
  1085. //==============================================================================
  1086. Slider::Slider()
  1087. {
  1088. init (LinearHorizontal, TextBoxLeft);
  1089. }
  1090. Slider::Slider (const String& name) : Component (name)
  1091. {
  1092. init (LinearHorizontal, TextBoxLeft);
  1093. }
  1094. Slider::Slider (SliderStyle style, TextEntryBoxPosition textBoxPos)
  1095. {
  1096. init (style, textBoxPos);
  1097. }
  1098. void Slider::init (SliderStyle style, TextEntryBoxPosition textBoxPos)
  1099. {
  1100. setWantsKeyboardFocus (false);
  1101. setRepaintsOnMouseActivity (true);
  1102. pimpl = new Pimpl (*this, style, textBoxPos);
  1103. Slider::lookAndFeelChanged();
  1104. updateText();
  1105. pimpl->registerListeners();
  1106. }
  1107. Slider::~Slider() {}
  1108. //==============================================================================
  1109. void Slider::addListener (Listener* l) { pimpl->listeners.add (l); }
  1110. void Slider::removeListener (Listener* l) { pimpl->listeners.remove (l); }
  1111. //==============================================================================
  1112. Slider::SliderStyle Slider::getSliderStyle() const noexcept { return pimpl->style; }
  1113. void Slider::setSliderStyle (const SliderStyle newStyle) { pimpl->setSliderStyle (newStyle); }
  1114. void Slider::setRotaryParameters (RotaryParameters p) noexcept
  1115. {
  1116. // make sure the values are sensible..
  1117. jassert (p.startAngleRadians >= 0 && p.endAngleRadians >= 0);
  1118. jassert (p.startAngleRadians < float_Pi * 4.0f && p.endAngleRadians < float_Pi * 4.0f);
  1119. pimpl->rotaryParams = p;
  1120. }
  1121. void Slider::setRotaryParameters (float startAngleRadians, float endAngleRadians, bool stopAtEnd) noexcept
  1122. {
  1123. RotaryParameters p = { startAngleRadians, endAngleRadians, stopAtEnd };
  1124. setRotaryParameters (p);
  1125. }
  1126. Slider::RotaryParameters Slider::getRotaryParameters() const noexcept
  1127. {
  1128. return pimpl->rotaryParams;
  1129. }
  1130. void Slider::setVelocityBasedMode (bool vb) { pimpl->isVelocityBased = vb; }
  1131. bool Slider::getVelocityBasedMode() const noexcept { return pimpl->isVelocityBased; }
  1132. bool Slider::getVelocityModeIsSwappable() const noexcept { return pimpl->userKeyOverridesVelocity; }
  1133. int Slider::getVelocityThreshold() const noexcept { return pimpl->velocityModeThreshold; }
  1134. double Slider::getVelocitySensitivity() const noexcept { return pimpl->velocityModeSensitivity; }
  1135. double Slider::getVelocityOffset() const noexcept { return pimpl->velocityModeOffset; }
  1136. void Slider::setVelocityModeParameters (const double sensitivity, const int threshold,
  1137. const double offset, const bool userCanPressKeyToSwapMode)
  1138. {
  1139. jassert (threshold >= 0);
  1140. jassert (sensitivity > 0);
  1141. jassert (offset >= 0);
  1142. pimpl->setVelocityModeParameters (sensitivity, threshold, offset, userCanPressKeyToSwapMode);
  1143. }
  1144. double Slider::getSkewFactor() const noexcept { return pimpl->skewFactor; }
  1145. bool Slider::isSymmetricSkew() const noexcept { return pimpl->symmetricSkew; }
  1146. void Slider::setSkewFactor (double factor, bool symmetricSkew)
  1147. {
  1148. pimpl->skewFactor = factor;
  1149. pimpl->symmetricSkew = symmetricSkew;
  1150. }
  1151. void Slider::setSkewFactorFromMidPoint (const double sliderValueToShowAtMidPoint)
  1152. {
  1153. pimpl->setSkewFactorFromMidPoint (sliderValueToShowAtMidPoint);
  1154. pimpl->symmetricSkew = false;
  1155. }
  1156. int Slider::getMouseDragSensitivity() const noexcept { return pimpl->pixelsForFullDragExtent; }
  1157. void Slider::setMouseDragSensitivity (const int distanceForFullScaleDrag)
  1158. {
  1159. jassert (distanceForFullScaleDrag > 0);
  1160. pimpl->pixelsForFullDragExtent = distanceForFullScaleDrag;
  1161. }
  1162. void Slider::setIncDecButtonsMode (const IncDecButtonMode mode) { pimpl->setIncDecButtonsMode (mode); }
  1163. Slider::TextEntryBoxPosition Slider::getTextBoxPosition() const noexcept { return pimpl->textBoxPos; }
  1164. int Slider::getTextBoxWidth() const noexcept { return pimpl->textBoxWidth; }
  1165. int Slider::getTextBoxHeight() const noexcept { return pimpl->textBoxHeight; }
  1166. void Slider::setTextBoxStyle (const TextEntryBoxPosition newPosition, const bool isReadOnly,
  1167. const int textEntryBoxWidth, const int textEntryBoxHeight)
  1168. {
  1169. pimpl->setTextBoxStyle (newPosition, isReadOnly, textEntryBoxWidth, textEntryBoxHeight);
  1170. }
  1171. bool Slider::isTextBoxEditable() const noexcept { return pimpl->editableText; }
  1172. void Slider::setTextBoxIsEditable (const bool shouldBeEditable) { pimpl->setTextBoxIsEditable (shouldBeEditable); }
  1173. void Slider::showTextBox() { pimpl->showTextBox(); }
  1174. void Slider::hideTextBox (const bool discardCurrentEditorContents) { pimpl->hideTextBox (discardCurrentEditorContents); }
  1175. void Slider::setChangeNotificationOnlyOnRelease (bool onlyNotifyOnRelease)
  1176. {
  1177. pimpl->sendChangeOnlyOnRelease = onlyNotifyOnRelease;
  1178. }
  1179. bool Slider::getSliderSnapsToMousePosition() const noexcept { return pimpl->snapsToMousePos; }
  1180. void Slider::setSliderSnapsToMousePosition (const bool shouldSnapToMouse) { pimpl->snapsToMousePos = shouldSnapToMouse; }
  1181. void Slider::setPopupDisplayEnabled (bool showOnDrag, bool showOnHover, Component* parent)
  1182. {
  1183. pimpl->showPopupOnDrag = showOnDrag;
  1184. pimpl->showPopupOnHover = showOnHover;
  1185. pimpl->parentForPopupDisplay = parent;
  1186. }
  1187. Component* Slider::getCurrentPopupDisplay() const noexcept { return pimpl->popupDisplay.get(); }
  1188. //==============================================================================
  1189. void Slider::colourChanged() { lookAndFeelChanged(); }
  1190. void Slider::lookAndFeelChanged() { pimpl->lookAndFeelChanged (getLookAndFeel()); }
  1191. void Slider::enablementChanged() { repaint(); pimpl->updateTextBoxEnablement(); }
  1192. //==============================================================================
  1193. double Slider::getMaximum() const noexcept { return pimpl->maximum; }
  1194. double Slider::getMinimum() const noexcept { return pimpl->minimum; }
  1195. double Slider::getInterval() const noexcept { return pimpl->interval; }
  1196. void Slider::setRange (double newMin, double newMax, double newInt)
  1197. {
  1198. pimpl->setRange (newMin, newMax, newInt);
  1199. }
  1200. Value& Slider::getValueObject() noexcept { return pimpl->currentValue; }
  1201. Value& Slider::getMinValueObject() noexcept { return pimpl->valueMin; }
  1202. Value& Slider::getMaxValueObject() noexcept { return pimpl->valueMax; }
  1203. double Slider::getValue() const { return pimpl->getValue(); }
  1204. void Slider::setValue (double newValue, const NotificationType notification)
  1205. {
  1206. pimpl->setValue (newValue, notification);
  1207. }
  1208. double Slider::getMinValue() const { return pimpl->getMinValue(); }
  1209. double Slider::getMaxValue() const { return pimpl->getMaxValue(); }
  1210. void Slider::setMinValue (double newValue, const NotificationType notification, bool allowNudgingOfOtherValues)
  1211. {
  1212. pimpl->setMinValue (newValue, notification, allowNudgingOfOtherValues);
  1213. }
  1214. void Slider::setMaxValue (double newValue, const NotificationType notification, bool allowNudgingOfOtherValues)
  1215. {
  1216. pimpl->setMaxValue (newValue, notification, allowNudgingOfOtherValues);
  1217. }
  1218. void Slider::setMinAndMaxValues (double newMinValue, double newMaxValue, const NotificationType notification)
  1219. {
  1220. pimpl->setMinAndMaxValues (newMinValue, newMaxValue, notification);
  1221. }
  1222. void Slider::setDoubleClickReturnValue (bool isDoubleClickEnabled, double valueToSetOnDoubleClick)
  1223. {
  1224. pimpl->doubleClickToValue = isDoubleClickEnabled;
  1225. pimpl->doubleClickReturnValue = valueToSetOnDoubleClick;
  1226. }
  1227. double Slider::getDoubleClickReturnValue() const noexcept { return pimpl->doubleClickReturnValue; }
  1228. bool Slider::isDoubleClickReturnEnabled() const noexcept { return pimpl->doubleClickToValue; }
  1229. void Slider::updateText()
  1230. {
  1231. pimpl->updateText();
  1232. }
  1233. void Slider::setTextValueSuffix (const String& suffix)
  1234. {
  1235. pimpl->setTextValueSuffix (suffix);
  1236. }
  1237. String Slider::getTextValueSuffix() const
  1238. {
  1239. return pimpl->textSuffix;
  1240. }
  1241. String Slider::getTextFromValue (double v)
  1242. {
  1243. if (getNumDecimalPlacesToDisplay() > 0)
  1244. return String (v, getNumDecimalPlacesToDisplay()) + getTextValueSuffix();
  1245. return String (roundToInt (v)) + getTextValueSuffix();
  1246. }
  1247. double Slider::getValueFromText (const String& text)
  1248. {
  1249. String t (text.trimStart());
  1250. if (t.endsWith (getTextValueSuffix()))
  1251. t = t.substring (0, t.length() - getTextValueSuffix().length());
  1252. while (t.startsWithChar ('+'))
  1253. t = t.substring (1).trimStart();
  1254. return t.initialSectionContainingOnly ("0123456789.,-")
  1255. .getDoubleValue();
  1256. }
  1257. double Slider::proportionOfLengthToValue (double proportion)
  1258. {
  1259. const double skew = getSkewFactor();
  1260. if (! isSymmetricSkew())
  1261. {
  1262. if (skew != 1.0 && proportion > 0.0)
  1263. proportion = std::exp (std::log (proportion) / skew);
  1264. return getMinimum() + (getMaximum() - getMinimum()) * proportion;
  1265. }
  1266. double distanceFromMiddle = 2.0 * proportion - 1.0;
  1267. if (skew != 1.0 && distanceFromMiddle != 0.0)
  1268. distanceFromMiddle = std::exp (std::log (std::abs (distanceFromMiddle)) / skew)
  1269. * (distanceFromMiddle < 0 ? -1 : 1);
  1270. return getMinimum() + (getMaximum() - getMinimum()) / 2.0 * (1 + distanceFromMiddle);
  1271. }
  1272. double Slider::valueToProportionOfLength (double value)
  1273. {
  1274. const double n = (value - getMinimum()) / (getMaximum() - getMinimum());
  1275. const double skew = getSkewFactor();
  1276. if (skew == 1.0)
  1277. return n;
  1278. if (! isSymmetricSkew())
  1279. return std::pow (n, skew);
  1280. double distanceFromMiddle = 2.0 * n - 1.0;
  1281. return (1.0 + std::pow (std::abs (distanceFromMiddle), skew) * (distanceFromMiddle < 0 ? -1 : 1)) / 2.0;
  1282. }
  1283. double Slider::snapValue (double attemptedValue, DragMode)
  1284. {
  1285. return attemptedValue;
  1286. }
  1287. int Slider::getNumDecimalPlacesToDisplay() const noexcept { return pimpl->numDecimalPlaces; }
  1288. //==============================================================================
  1289. int Slider::getThumbBeingDragged() const noexcept { return pimpl->sliderBeingDragged; }
  1290. void Slider::startedDragging() {}
  1291. void Slider::stoppedDragging() {}
  1292. void Slider::valueChanged() {}
  1293. //==============================================================================
  1294. void Slider::setPopupMenuEnabled (const bool menuEnabled) { pimpl->menuEnabled = menuEnabled; }
  1295. void Slider::setScrollWheelEnabled (const bool enabled) { pimpl->scrollWheelEnabled = enabled; }
  1296. bool Slider::isHorizontal() const noexcept { return pimpl->isHorizontal(); }
  1297. bool Slider::isVertical() const noexcept { return pimpl->isVertical(); }
  1298. bool Slider::isRotary() const noexcept { return pimpl->isRotary(); }
  1299. bool Slider::isBar() const noexcept { return pimpl->isBar(); }
  1300. float Slider::getPositionOfValue (const double value) const { return pimpl->getPositionOfValue (value); }
  1301. //==============================================================================
  1302. void Slider::paint (Graphics& g) { pimpl->paint (g, getLookAndFeel()); }
  1303. void Slider::resized() { pimpl->resized (getLookAndFeel()); }
  1304. void Slider::focusOfChildComponentChanged (FocusChangeType) { repaint(); }
  1305. void Slider::mouseDown (const MouseEvent& e) { pimpl->mouseDown (e); }
  1306. void Slider::mouseUp (const MouseEvent&) { pimpl->mouseUp(); }
  1307. void Slider::mouseMove (const MouseEvent&) { pimpl->mouseMove(); }
  1308. void Slider::mouseExit (const MouseEvent&) { pimpl->mouseExit(); }
  1309. // If popup display is enabled and set to show on mouse hover, this makes sure
  1310. // it is shown when dragging the mouse over a slider and releasing
  1311. void Slider::mouseEnter (const MouseEvent&) { pimpl->mouseMove(); }
  1312. void Slider::modifierKeysChanged (const ModifierKeys& modifiers)
  1313. {
  1314. if (isEnabled())
  1315. pimpl->modifierKeysChanged (modifiers);
  1316. }
  1317. void Slider::mouseDrag (const MouseEvent& e)
  1318. {
  1319. if (isEnabled())
  1320. pimpl->mouseDrag (e);
  1321. }
  1322. void Slider::mouseDoubleClick (const MouseEvent&)
  1323. {
  1324. if (isEnabled())
  1325. pimpl->mouseDoubleClick();
  1326. }
  1327. void Slider::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
  1328. {
  1329. if (! (isEnabled() && pimpl->mouseWheelMove (e, wheel)))
  1330. Component::mouseWheelMove (e, wheel);
  1331. }
  1332. } // namespace juce