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.

1675 lines
60KB

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