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.

445 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. using Button::clicked;
  39. int direction;
  40. private:
  41. ScrollBar& owner;
  42. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScrollbarButton)
  43. };
  44. //==============================================================================
  45. ScrollBar::ScrollBar (bool shouldBeVertical) : vertical (shouldBeVertical)
  46. {
  47. setRepaintsOnMouseActivity (true);
  48. setFocusContainer (true);
  49. }
  50. ScrollBar::~ScrollBar()
  51. {
  52. upButton.reset();
  53. downButton.reset();
  54. }
  55. //==============================================================================
  56. void ScrollBar::setRangeLimits (Range<double> newRangeLimit, NotificationType notification)
  57. {
  58. if (totalRange != newRangeLimit)
  59. {
  60. totalRange = newRangeLimit;
  61. setCurrentRange (visibleRange, notification);
  62. updateThumbPosition();
  63. }
  64. }
  65. void ScrollBar::setRangeLimits (double newMinimum, double newMaximum, NotificationType notification)
  66. {
  67. jassert (newMaximum >= newMinimum); // these can't be the wrong way round!
  68. setRangeLimits (Range<double> (newMinimum, newMaximum), notification);
  69. }
  70. bool ScrollBar::setCurrentRange (Range<double> newRange, NotificationType notification)
  71. {
  72. auto constrainedRange = totalRange.constrainRange (newRange);
  73. if (visibleRange != constrainedRange)
  74. {
  75. visibleRange = constrainedRange;
  76. updateThumbPosition();
  77. if (notification != dontSendNotification)
  78. triggerAsyncUpdate();
  79. if (notification == sendNotificationSync)
  80. handleUpdateNowIfNeeded();
  81. return true;
  82. }
  83. return false;
  84. }
  85. void ScrollBar::setCurrentRange (double newStart, double newSize, NotificationType notification)
  86. {
  87. setCurrentRange (Range<double> (newStart, newStart + newSize), notification);
  88. }
  89. void ScrollBar::setCurrentRangeStart (double newStart, NotificationType notification)
  90. {
  91. setCurrentRange (visibleRange.movedToStartAt (newStart), notification);
  92. }
  93. void ScrollBar::setSingleStepSize (double newSingleStepSize) noexcept
  94. {
  95. singleStepSize = newSingleStepSize;
  96. }
  97. bool ScrollBar::moveScrollbarInSteps (int howManySteps, NotificationType notification)
  98. {
  99. return setCurrentRange (visibleRange + howManySteps * singleStepSize, notification);
  100. }
  101. bool ScrollBar::moveScrollbarInPages (int howManyPages, NotificationType notification)
  102. {
  103. return setCurrentRange (visibleRange + howManyPages * visibleRange.getLength(), notification);
  104. }
  105. bool ScrollBar::scrollToTop (NotificationType notification)
  106. {
  107. return setCurrentRange (visibleRange.movedToStartAt (getMinimumRangeLimit()), notification);
  108. }
  109. bool ScrollBar::scrollToBottom (NotificationType notification)
  110. {
  111. return setCurrentRange (visibleRange.movedToEndAt (getMaximumRangeLimit()), notification);
  112. }
  113. void ScrollBar::setButtonRepeatSpeed (int newInitialDelay,
  114. int newRepeatDelay,
  115. int newMinimumDelay)
  116. {
  117. initialDelayInMillisecs = newInitialDelay;
  118. repeatDelayInMillisecs = newRepeatDelay;
  119. minimumDelayInMillisecs = newMinimumDelay;
  120. if (upButton != nullptr)
  121. {
  122. upButton ->setRepeatSpeed (newInitialDelay, newRepeatDelay, newMinimumDelay);
  123. downButton->setRepeatSpeed (newInitialDelay, newRepeatDelay, newMinimumDelay);
  124. }
  125. }
  126. //==============================================================================
  127. void ScrollBar::addListener (Listener* listener)
  128. {
  129. listeners.add (listener);
  130. }
  131. void ScrollBar::removeListener (Listener* listener)
  132. {
  133. listeners.remove (listener);
  134. }
  135. void ScrollBar::handleAsyncUpdate()
  136. {
  137. auto start = visibleRange.getStart(); // (need to use a temp variable for VC7 compatibility)
  138. listeners.call ([=] (Listener& l) { l.scrollBarMoved (this, start); });
  139. }
  140. //==============================================================================
  141. void ScrollBar::updateThumbPosition()
  142. {
  143. auto minimumScrollBarThumbSize = getLookAndFeel().getMinimumScrollbarThumbSize (*this);
  144. int newThumbSize = roundToInt (totalRange.getLength() > 0 ? (visibleRange.getLength() * thumbAreaSize) / totalRange.getLength()
  145. : thumbAreaSize);
  146. if (newThumbSize < minimumScrollBarThumbSize)
  147. newThumbSize = jmin (minimumScrollBarThumbSize, thumbAreaSize - 1);
  148. if (newThumbSize > thumbAreaSize)
  149. newThumbSize = thumbAreaSize;
  150. int newThumbStart = thumbAreaStart;
  151. if (totalRange.getLength() > visibleRange.getLength())
  152. newThumbStart += roundToInt (((visibleRange.getStart() - totalRange.getStart()) * (thumbAreaSize - newThumbSize))
  153. / (totalRange.getLength() - visibleRange.getLength()));
  154. Component::setVisible (getVisibility());
  155. if (thumbStart != newThumbStart || thumbSize != newThumbSize)
  156. {
  157. auto repaintStart = jmin (thumbStart, newThumbStart) - 4;
  158. auto 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 (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 (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. auto& lf = getLookAndFeel();
  195. auto 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. auto length = vertical ? getHeight() : getWidth();
  214. auto& lf = getLookAndFeel();
  215. bool buttonsVisible = lf.areScrollbarButtonsVisible();
  216. int buttonSize = 0;
  217. if (buttonsVisible)
  218. {
  219. if (upButton == nullptr)
  220. {
  221. upButton .reset (new ScrollbarButton (vertical ? 0 : 3, *this));
  222. downButton.reset (new ScrollbarButton (vertical ? 2 : 1, *this));
  223. addAndMakeVisible (upButton.get());
  224. addAndMakeVisible (downButton.get());
  225. setButtonRepeatSpeed (initialDelayInMillisecs, repeatDelayInMillisecs, minimumDelayInMillisecs);
  226. }
  227. buttonSize = jmin (lf.getScrollbarButtonSize (*this), length / 2);
  228. }
  229. else
  230. {
  231. upButton.reset();
  232. downButton.reset();
  233. }
  234. if (length < 32 + lf.getMinimumScrollbarThumbSize (*this))
  235. {
  236. thumbAreaStart = length / 2;
  237. thumbAreaSize = 0;
  238. }
  239. else
  240. {
  241. thumbAreaStart = buttonSize;
  242. thumbAreaSize = length - 2 * buttonSize;
  243. }
  244. if (upButton != nullptr)
  245. {
  246. auto r = getLocalBounds();
  247. if (vertical)
  248. {
  249. upButton->setBounds (r.removeFromTop (buttonSize));
  250. downButton->setBounds (r.removeFromBottom (buttonSize));
  251. }
  252. else
  253. {
  254. upButton->setBounds (r.removeFromLeft (buttonSize));
  255. downButton->setBounds (r.removeFromRight (buttonSize));
  256. }
  257. }
  258. updateThumbPosition();
  259. }
  260. void ScrollBar::parentHierarchyChanged()
  261. {
  262. lookAndFeelChanged();
  263. }
  264. void ScrollBar::mouseDown (const MouseEvent& e)
  265. {
  266. isDraggingThumb = false;
  267. lastMousePos = vertical ? e.y : e.x;
  268. dragStartMousePos = lastMousePos;
  269. dragStartRange = visibleRange.getStart();
  270. if (dragStartMousePos < thumbStart)
  271. {
  272. moveScrollbarInPages (-1);
  273. startTimer (400);
  274. }
  275. else if (dragStartMousePos >= thumbStart + thumbSize)
  276. {
  277. moveScrollbarInPages (1);
  278. startTimer (400);
  279. }
  280. else
  281. {
  282. isDraggingThumb = (thumbAreaSize > getLookAndFeel().getMinimumScrollbarThumbSize (*this))
  283. && (thumbAreaSize > thumbSize);
  284. }
  285. }
  286. void ScrollBar::mouseDrag (const MouseEvent& e)
  287. {
  288. auto mousePos = vertical ? e.y : e.x;
  289. if (isDraggingThumb && lastMousePos != mousePos && thumbAreaSize > thumbSize)
  290. {
  291. auto deltaPixels = mousePos - dragStartMousePos;
  292. setCurrentRangeStart (dragStartRange
  293. + deltaPixels * (totalRange.getLength() - visibleRange.getLength())
  294. / (thumbAreaSize - thumbSize));
  295. }
  296. lastMousePos = mousePos;
  297. }
  298. void ScrollBar::mouseUp (const MouseEvent&)
  299. {
  300. isDraggingThumb = false;
  301. stopTimer();
  302. repaint();
  303. }
  304. void ScrollBar::mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel)
  305. {
  306. float increment = 10.0f * (vertical ? wheel.deltaY : wheel.deltaX);
  307. if (increment < 0)
  308. increment = jmin (increment, -1.0f);
  309. else if (increment > 0)
  310. increment = jmax (increment, 1.0f);
  311. setCurrentRange (visibleRange - singleStepSize * increment);
  312. }
  313. void ScrollBar::timerCallback()
  314. {
  315. if (isMouseButtonDown())
  316. {
  317. startTimer (40);
  318. if (lastMousePos < thumbStart)
  319. setCurrentRange (visibleRange - visibleRange.getLength());
  320. else if (lastMousePos > thumbStart + thumbSize)
  321. setCurrentRangeStart (visibleRange.getEnd());
  322. }
  323. else
  324. {
  325. stopTimer();
  326. }
  327. }
  328. bool ScrollBar::keyPressed (const KeyPress& key)
  329. {
  330. if (isVisible())
  331. {
  332. if (key == KeyPress::upKey || key == KeyPress::leftKey) return moveScrollbarInSteps (-1);
  333. if (key == KeyPress::downKey || key == KeyPress::rightKey) return moveScrollbarInSteps (1);
  334. if (key == KeyPress::pageUpKey) return moveScrollbarInPages (-1);
  335. if (key == KeyPress::pageDownKey) return moveScrollbarInPages (1);
  336. if (key == KeyPress::homeKey) return scrollToTop();
  337. if (key == KeyPress::endKey) return scrollToBottom();
  338. }
  339. return false;
  340. }
  341. void ScrollBar::setVisible (bool shouldBeVisible)
  342. {
  343. if (userVisibilityFlag != shouldBeVisible)
  344. {
  345. userVisibilityFlag = shouldBeVisible;
  346. Component::setVisible (getVisibility());
  347. }
  348. }
  349. bool ScrollBar::getVisibility() const noexcept
  350. {
  351. if (! userVisibilityFlag)
  352. return false;
  353. return (! autohides) || (totalRange.getLength() > visibleRange.getLength()
  354. && visibleRange.getLength() > 0.0);
  355. }
  356. } // namespace juce