Audio plugin host https://kx.studio/carla
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.

1803 lines
65KB

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