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.

423 lines
13KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace juce
  20. {
  21. class ScrollBar::ScrollbarButton : public Button
  22. {
  23. public:
  24. ScrollbarButton (int direc, ScrollBar& s)
  25. : Button (String()), direction (direc), owner (s)
  26. {
  27. setWantsKeyboardFocus (false);
  28. }
  29. void paintButton (Graphics& g, bool over, bool down) override
  30. {
  31. getLookAndFeel().drawScrollbarButton (g, owner, getWidth(), getHeight(),
  32. direction, owner.isVertical(), over, down);
  33. }
  34. void clicked() override
  35. {
  36. owner.moveScrollbarInSteps ((direction == 1 || direction == 2) ? 1 : -1);
  37. }
  38. int direction;
  39. private:
  40. ScrollBar& owner;
  41. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScrollbarButton)
  42. };
  43. //==============================================================================
  44. ScrollBar::ScrollBar (bool shouldBeVertical) : vertical (shouldBeVertical)
  45. {
  46. setRepaintsOnMouseActivity (true);
  47. setFocusContainer (true);
  48. }
  49. ScrollBar::~ScrollBar()
  50. {
  51. upButton.reset();
  52. downButton.reset();
  53. }
  54. //==============================================================================
  55. void ScrollBar::setRangeLimits (Range<double> newRangeLimit, NotificationType notification)
  56. {
  57. if (totalRange != newRangeLimit)
  58. {
  59. totalRange = newRangeLimit;
  60. setCurrentRange (visibleRange, notification);
  61. updateThumbPosition();
  62. }
  63. }
  64. void ScrollBar::setRangeLimits (const double newMinimum, const double newMaximum, NotificationType notification)
  65. {
  66. jassert (newMaximum >= newMinimum); // these can't be the wrong way round!
  67. setRangeLimits (Range<double> (newMinimum, newMaximum), notification);
  68. }
  69. bool ScrollBar::setCurrentRange (Range<double> newRange, const NotificationType notification)
  70. {
  71. const Range<double> constrainedRange (totalRange.constrainRange (newRange));
  72. if (visibleRange != constrainedRange)
  73. {
  74. visibleRange = constrainedRange;
  75. updateThumbPosition();
  76. if (notification != dontSendNotification)
  77. triggerAsyncUpdate();
  78. if (notification == sendNotificationSync)
  79. handleUpdateNowIfNeeded();
  80. return true;
  81. }
  82. return false;
  83. }
  84. void ScrollBar::setCurrentRange (const double newStart, const double newSize, NotificationType notification)
  85. {
  86. setCurrentRange (Range<double> (newStart, newStart + newSize), notification);
  87. }
  88. void ScrollBar::setCurrentRangeStart (const double newStart, NotificationType notification)
  89. {
  90. setCurrentRange (visibleRange.movedToStartAt (newStart), notification);
  91. }
  92. void ScrollBar::setSingleStepSize (const double newSingleStepSize) noexcept
  93. {
  94. singleStepSize = newSingleStepSize;
  95. }
  96. bool ScrollBar::moveScrollbarInSteps (const int howManySteps, NotificationType notification)
  97. {
  98. return setCurrentRange (visibleRange + howManySteps * singleStepSize, notification);
  99. }
  100. bool ScrollBar::moveScrollbarInPages (const int howManyPages, NotificationType notification)
  101. {
  102. return setCurrentRange (visibleRange + howManyPages * visibleRange.getLength(), notification);
  103. }
  104. bool ScrollBar::scrollToTop (NotificationType notification)
  105. {
  106. return setCurrentRange (visibleRange.movedToStartAt (getMinimumRangeLimit()), notification);
  107. }
  108. bool ScrollBar::scrollToBottom (NotificationType notification)
  109. {
  110. return setCurrentRange (visibleRange.movedToEndAt (getMaximumRangeLimit()), notification);
  111. }
  112. void ScrollBar::setButtonRepeatSpeed (const int newInitialDelay,
  113. const int newRepeatDelay,
  114. const int newMinimumDelay)
  115. {
  116. initialDelayInMillisecs = newInitialDelay;
  117. repeatDelayInMillisecs = newRepeatDelay;
  118. minimumDelayInMillisecs = newMinimumDelay;
  119. if (upButton != nullptr)
  120. {
  121. upButton ->setRepeatSpeed (newInitialDelay, newRepeatDelay, newMinimumDelay);
  122. downButton->setRepeatSpeed (newInitialDelay, newRepeatDelay, newMinimumDelay);
  123. }
  124. }
  125. //==============================================================================
  126. void ScrollBar::addListener (Listener* const listener)
  127. {
  128. listeners.add (listener);
  129. }
  130. void ScrollBar::removeListener (Listener* const listener)
  131. {
  132. listeners.remove (listener);
  133. }
  134. void ScrollBar::handleAsyncUpdate()
  135. {
  136. double start = visibleRange.getStart(); // (need to use a temp variable for VC7 compatibility)
  137. listeners.call (&ScrollBar::Listener::scrollBarMoved, this, start);
  138. }
  139. //==============================================================================
  140. void ScrollBar::updateThumbPosition()
  141. {
  142. const int minimumScrollBarThumbSize = getLookAndFeel().getMinimumScrollbarThumbSize (*this);
  143. int newThumbSize = roundToInt (totalRange.getLength() > 0 ? (visibleRange.getLength() * thumbAreaSize) / totalRange.getLength()
  144. : thumbAreaSize);
  145. if (newThumbSize < minimumScrollBarThumbSize)
  146. newThumbSize = jmin (minimumScrollBarThumbSize, thumbAreaSize - 1);
  147. if (newThumbSize > thumbAreaSize)
  148. newThumbSize = thumbAreaSize;
  149. int newThumbStart = thumbAreaStart;
  150. if (totalRange.getLength() > visibleRange.getLength())
  151. newThumbStart += roundToInt (((visibleRange.getStart() - totalRange.getStart()) * (thumbAreaSize - newThumbSize))
  152. / (totalRange.getLength() - visibleRange.getLength()));
  153. setVisible ((! autohides) || (totalRange.getLength() > visibleRange.getLength()
  154. && visibleRange.getLength() > 0.0));
  155. if (thumbStart != newThumbStart || thumbSize != newThumbSize)
  156. {
  157. const int repaintStart = jmin (thumbStart, newThumbStart) - 4;
  158. const int repaintSize = jmax (thumbStart + thumbSize, newThumbStart + newThumbSize) + 8 - repaintStart;
  159. if (vertical)
  160. repaint (0, repaintStart, getWidth(), repaintSize);
  161. else
  162. repaint (repaintStart, 0, repaintSize, getHeight());
  163. thumbStart = newThumbStart;
  164. thumbSize = newThumbSize;
  165. }
  166. }
  167. void ScrollBar::setOrientation (const bool shouldBeVertical)
  168. {
  169. if (vertical != shouldBeVertical)
  170. {
  171. vertical = shouldBeVertical;
  172. if (upButton != nullptr)
  173. {
  174. upButton->direction = vertical ? 0 : 3;
  175. downButton->direction = vertical ? 2 : 1;
  176. }
  177. updateThumbPosition();
  178. }
  179. }
  180. void ScrollBar::setAutoHide (const bool shouldHideWhenFullRange)
  181. {
  182. autohides = shouldHideWhenFullRange;
  183. updateThumbPosition();
  184. }
  185. bool ScrollBar::autoHides() const noexcept
  186. {
  187. return autohides;
  188. }
  189. //==============================================================================
  190. void ScrollBar::paint (Graphics& g)
  191. {
  192. if (thumbAreaSize > 0)
  193. {
  194. LookAndFeel& lf = getLookAndFeel();
  195. const int thumb = (thumbAreaSize > lf.getMinimumScrollbarThumbSize (*this))
  196. ? thumbSize : 0;
  197. if (vertical)
  198. lf.drawScrollbar (g, *this, 0, thumbAreaStart, getWidth(), thumbAreaSize,
  199. vertical, thumbStart, thumb, isMouseOver(), isMouseButtonDown());
  200. else
  201. lf.drawScrollbar (g, *this, thumbAreaStart, 0, thumbAreaSize, getHeight(),
  202. vertical, thumbStart, thumb, isMouseOver(), isMouseButtonDown());
  203. }
  204. }
  205. void ScrollBar::lookAndFeelChanged()
  206. {
  207. setComponentEffect (getLookAndFeel().getScrollbarEffect());
  208. if (isVisible())
  209. resized();
  210. }
  211. void ScrollBar::resized()
  212. {
  213. const int length = vertical ? getHeight() : getWidth();
  214. LookAndFeel& lf = getLookAndFeel();
  215. const bool buttonsVisible = lf.areScrollbarButtonsVisible();
  216. int buttonSize = 0;
  217. if (buttonsVisible)
  218. {
  219. if (upButton == nullptr)
  220. {
  221. addAndMakeVisible (upButton = new ScrollbarButton (vertical ? 0 : 3, *this));
  222. addAndMakeVisible (downButton = new ScrollbarButton (vertical ? 2 : 1, *this));
  223. setButtonRepeatSpeed (initialDelayInMillisecs, repeatDelayInMillisecs, minimumDelayInMillisecs);
  224. }
  225. buttonSize = jmin (lf.getScrollbarButtonSize (*this), length / 2);
  226. }
  227. else
  228. {
  229. upButton.reset();
  230. downButton.reset();
  231. }
  232. if (length < 32 + lf.getMinimumScrollbarThumbSize (*this))
  233. {
  234. thumbAreaStart = length / 2;
  235. thumbAreaSize = 0;
  236. }
  237. else
  238. {
  239. thumbAreaStart = buttonSize;
  240. thumbAreaSize = length - 2 * buttonSize;
  241. }
  242. if (upButton != nullptr)
  243. {
  244. Rectangle<int> r (getLocalBounds());
  245. if (vertical)
  246. {
  247. upButton->setBounds (r.removeFromTop (buttonSize));
  248. downButton->setBounds (r.removeFromBottom (buttonSize));
  249. }
  250. else
  251. {
  252. upButton->setBounds (r.removeFromLeft (buttonSize));
  253. downButton->setBounds (r.removeFromRight (buttonSize));
  254. }
  255. }
  256. updateThumbPosition();
  257. }
  258. void ScrollBar::parentHierarchyChanged()
  259. {
  260. lookAndFeelChanged();
  261. }
  262. void ScrollBar::mouseDown (const MouseEvent& e)
  263. {
  264. isDraggingThumb = false;
  265. lastMousePos = vertical ? e.y : e.x;
  266. dragStartMousePos = lastMousePos;
  267. dragStartRange = visibleRange.getStart();
  268. if (dragStartMousePos < thumbStart)
  269. {
  270. moveScrollbarInPages (-1);
  271. startTimer (400);
  272. }
  273. else if (dragStartMousePos >= thumbStart + thumbSize)
  274. {
  275. moveScrollbarInPages (1);
  276. startTimer (400);
  277. }
  278. else
  279. {
  280. isDraggingThumb = (thumbAreaSize > getLookAndFeel().getMinimumScrollbarThumbSize (*this))
  281. && (thumbAreaSize > thumbSize);
  282. }
  283. }
  284. void ScrollBar::mouseDrag (const MouseEvent& e)
  285. {
  286. const int mousePos = vertical ? e.y : e.x;
  287. if (isDraggingThumb && lastMousePos != mousePos && thumbAreaSize > thumbSize)
  288. {
  289. const int deltaPixels = mousePos - dragStartMousePos;
  290. setCurrentRangeStart (dragStartRange
  291. + deltaPixels * (totalRange.getLength() - visibleRange.getLength())
  292. / (thumbAreaSize - thumbSize));
  293. }
  294. lastMousePos = mousePos;
  295. }
  296. void ScrollBar::mouseUp (const MouseEvent&)
  297. {
  298. isDraggingThumb = false;
  299. stopTimer();
  300. repaint();
  301. }
  302. void ScrollBar::mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel)
  303. {
  304. float increment = 10.0f * (vertical ? wheel.deltaY : wheel.deltaX);
  305. if (increment < 0)
  306. increment = jmin (increment, -1.0f);
  307. else if (increment > 0)
  308. increment = jmax (increment, 1.0f);
  309. setCurrentRange (visibleRange - singleStepSize * increment);
  310. }
  311. void ScrollBar::timerCallback()
  312. {
  313. if (isMouseButtonDown())
  314. {
  315. startTimer (40);
  316. if (lastMousePos < thumbStart)
  317. setCurrentRange (visibleRange - visibleRange.getLength());
  318. else if (lastMousePos > thumbStart + thumbSize)
  319. setCurrentRangeStart (visibleRange.getEnd());
  320. }
  321. else
  322. {
  323. stopTimer();
  324. }
  325. }
  326. bool ScrollBar::keyPressed (const KeyPress& key)
  327. {
  328. if (isVisible())
  329. {
  330. if (key == KeyPress::upKey || key == KeyPress::leftKey) return moveScrollbarInSteps (-1);
  331. if (key == KeyPress::downKey || key == KeyPress::rightKey) return moveScrollbarInSteps (1);
  332. if (key == KeyPress::pageUpKey) return moveScrollbarInPages (-1);
  333. if (key == KeyPress::pageDownKey) return moveScrollbarInPages (1);
  334. if (key == KeyPress::homeKey) return scrollToTop();
  335. if (key == KeyPress::endKey) return scrollToBottom();
  336. }
  337. return false;
  338. }
  339. } // namespace juce