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.

juce_Slider.cpp 61KB

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