The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

1597 lines
58KB

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