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.

1769 lines
64KB

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