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.

1828 lines
66KB

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