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.

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