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.

1652 lines
59KB

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