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_Viewport.cpp 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. Viewport::Viewport (const String& name)
  18. : Component (name),
  19. scrollBarThickness (0),
  20. singleStepX (16),
  21. singleStepY (16),
  22. showHScrollbar (true),
  23. showVScrollbar (true),
  24. deleteContent (true),
  25. verticalScrollBar (true),
  26. horizontalScrollBar (false)
  27. {
  28. // content holder is used to clip the contents so they don't overlap the scrollbars
  29. addAndMakeVisible (&contentHolder);
  30. contentHolder.setInterceptsMouseClicks (false, true);
  31. addChildComponent (&verticalScrollBar);
  32. addChildComponent (&horizontalScrollBar);
  33. verticalScrollBar.addListener (this);
  34. horizontalScrollBar.addListener (this);
  35. setInterceptsMouseClicks (false, true);
  36. setWantsKeyboardFocus (true);
  37. }
  38. Viewport::~Viewport()
  39. {
  40. deleteContentComp();
  41. }
  42. //==============================================================================
  43. void Viewport::visibleAreaChanged (const Rectangle<int>&) {}
  44. void Viewport::viewedComponentChanged (Component*) {}
  45. //==============================================================================
  46. void Viewport::deleteContentComp()
  47. {
  48. if (contentComp != nullptr)
  49. contentComp->removeComponentListener (this);
  50. if (deleteContent)
  51. {
  52. // This sets the content comp to a null pointer before deleting the old one, in case
  53. // anything tries to use the old one while it's in mid-deletion..
  54. ScopedPointer<Component> oldCompDeleter (contentComp);
  55. }
  56. else
  57. {
  58. contentComp = nullptr;
  59. }
  60. }
  61. void Viewport::setViewedComponent (Component* const newViewedComponent, const bool deleteComponentWhenNoLongerNeeded)
  62. {
  63. if (contentComp.get() != newViewedComponent)
  64. {
  65. deleteContentComp();
  66. contentComp = newViewedComponent;
  67. deleteContent = deleteComponentWhenNoLongerNeeded;
  68. if (contentComp != nullptr)
  69. {
  70. contentHolder.addAndMakeVisible (contentComp);
  71. setViewPosition (Point<int>());
  72. contentComp->addComponentListener (this);
  73. }
  74. viewedComponentChanged (contentComp);
  75. updateVisibleArea();
  76. }
  77. }
  78. int Viewport::getMaximumVisibleWidth() const { return contentHolder.getWidth(); }
  79. int Viewport::getMaximumVisibleHeight() const { return contentHolder.getHeight(); }
  80. Point<int> Viewport::viewportPosToCompPos (Point<int> pos) const
  81. {
  82. jassert (contentComp != nullptr);
  83. return Point<int> (jmax (jmin (0, contentHolder.getWidth() - contentComp->getWidth()), jmin (0, -(pos.x))),
  84. jmax (jmin (0, contentHolder.getHeight() - contentComp->getHeight()), jmin (0, -(pos.y))));
  85. }
  86. void Viewport::setViewPosition (const int xPixelsOffset, const int yPixelsOffset)
  87. {
  88. setViewPosition (Point<int> (xPixelsOffset, yPixelsOffset));
  89. }
  90. void Viewport::setViewPosition (Point<int> newPosition)
  91. {
  92. if (contentComp != nullptr)
  93. contentComp->setTopLeftPosition (viewportPosToCompPos (newPosition));
  94. }
  95. void Viewport::setViewPositionProportionately (const double x, const double y)
  96. {
  97. if (contentComp != nullptr)
  98. setViewPosition (jmax (0, roundToInt (x * (contentComp->getWidth() - getWidth()))),
  99. jmax (0, roundToInt (y * (contentComp->getHeight() - getHeight()))));
  100. }
  101. bool Viewport::autoScroll (const int mouseX, const int mouseY, const int activeBorderThickness, const int maximumSpeed)
  102. {
  103. if (contentComp != nullptr)
  104. {
  105. int dx = 0, dy = 0;
  106. if (horizontalScrollBar.isVisible() || contentComp->getX() < 0 || contentComp->getRight() > getWidth())
  107. {
  108. if (mouseX < activeBorderThickness)
  109. dx = activeBorderThickness - mouseX;
  110. else if (mouseX >= contentHolder.getWidth() - activeBorderThickness)
  111. dx = (contentHolder.getWidth() - activeBorderThickness) - mouseX;
  112. if (dx < 0)
  113. dx = jmax (dx, -maximumSpeed, contentHolder.getWidth() - contentComp->getRight());
  114. else
  115. dx = jmin (dx, maximumSpeed, -contentComp->getX());
  116. }
  117. if (verticalScrollBar.isVisible() || contentComp->getY() < 0 || contentComp->getBottom() > getHeight())
  118. {
  119. if (mouseY < activeBorderThickness)
  120. dy = activeBorderThickness - mouseY;
  121. else if (mouseY >= contentHolder.getHeight() - activeBorderThickness)
  122. dy = (contentHolder.getHeight() - activeBorderThickness) - mouseY;
  123. if (dy < 0)
  124. dy = jmax (dy, -maximumSpeed, contentHolder.getHeight() - contentComp->getBottom());
  125. else
  126. dy = jmin (dy, maximumSpeed, -contentComp->getY());
  127. }
  128. if (dx != 0 || dy != 0)
  129. {
  130. contentComp->setTopLeftPosition (contentComp->getX() + dx,
  131. contentComp->getY() + dy);
  132. return true;
  133. }
  134. }
  135. return false;
  136. }
  137. void Viewport::componentMovedOrResized (Component&, bool, bool)
  138. {
  139. updateVisibleArea();
  140. }
  141. void Viewport::resized()
  142. {
  143. updateVisibleArea();
  144. }
  145. //==============================================================================
  146. void Viewport::updateVisibleArea()
  147. {
  148. const int scrollbarWidth = getScrollBarThickness();
  149. const bool canShowAnyBars = getWidth() > scrollbarWidth && getHeight() > scrollbarWidth;
  150. const bool canShowHBar = showHScrollbar && canShowAnyBars;
  151. const bool canShowVBar = showVScrollbar && canShowAnyBars;
  152. bool hBarVisible = false, vBarVisible = false;
  153. Rectangle<int> contentArea;
  154. for (int i = 3; --i >= 0;)
  155. {
  156. hBarVisible = canShowHBar && ! horizontalScrollBar.autoHides();
  157. vBarVisible = canShowVBar && ! verticalScrollBar.autoHides();
  158. contentArea = getLocalBounds();
  159. if (contentComp != nullptr && ! contentArea.contains (contentComp->getBounds()))
  160. {
  161. hBarVisible = canShowHBar && (hBarVisible || contentComp->getX() < 0 || contentComp->getRight() > contentArea.getWidth());
  162. vBarVisible = canShowVBar && (vBarVisible || contentComp->getY() < 0 || contentComp->getBottom() > contentArea.getHeight());
  163. if (vBarVisible)
  164. contentArea.setWidth (getWidth() - scrollbarWidth);
  165. if (hBarVisible)
  166. contentArea.setHeight (getHeight() - scrollbarWidth);
  167. if (! contentArea.contains (contentComp->getBounds()))
  168. {
  169. hBarVisible = canShowHBar && (hBarVisible || contentComp->getRight() > contentArea.getWidth());
  170. vBarVisible = canShowVBar && (vBarVisible || contentComp->getBottom() > contentArea.getHeight());
  171. }
  172. }
  173. if (vBarVisible) contentArea.setWidth (getWidth() - scrollbarWidth);
  174. if (hBarVisible) contentArea.setHeight (getHeight() - scrollbarWidth);
  175. if (contentComp == nullptr)
  176. {
  177. contentHolder.setBounds (contentArea);
  178. break;
  179. }
  180. const Rectangle<int> oldContentBounds (contentComp->getBounds());
  181. contentHolder.setBounds (contentArea);
  182. // If the content has changed its size, that might affect our scrollbars, so go round again and re-caclulate..
  183. if (oldContentBounds == contentComp->getBounds())
  184. break;
  185. }
  186. Rectangle<int> contentBounds;
  187. if (contentComp != nullptr)
  188. contentBounds = contentHolder.getLocalArea (contentComp, contentComp->getLocalBounds());
  189. Point<int> visibleOrigin (-contentBounds.getPosition());
  190. if (hBarVisible)
  191. {
  192. horizontalScrollBar.setBounds (0, contentArea.getHeight(), contentArea.getWidth(), scrollbarWidth);
  193. horizontalScrollBar.setRangeLimits (0.0, contentBounds.getWidth());
  194. horizontalScrollBar.setCurrentRange (visibleOrigin.x, contentArea.getWidth());
  195. horizontalScrollBar.setSingleStepSize (singleStepX);
  196. horizontalScrollBar.cancelPendingUpdate();
  197. }
  198. else if (canShowHBar)
  199. {
  200. visibleOrigin.setX (0);
  201. }
  202. if (vBarVisible)
  203. {
  204. verticalScrollBar.setBounds (contentArea.getWidth(), 0, scrollbarWidth, contentArea.getHeight());
  205. verticalScrollBar.setRangeLimits (0.0, contentBounds.getHeight());
  206. verticalScrollBar.setCurrentRange (visibleOrigin.y, contentArea.getHeight());
  207. verticalScrollBar.setSingleStepSize (singleStepY);
  208. verticalScrollBar.cancelPendingUpdate();
  209. }
  210. else if (canShowVBar)
  211. {
  212. visibleOrigin.setY (0);
  213. }
  214. // Force the visibility *after* setting the ranges to avoid flicker caused by edge conditions in the numbers.
  215. horizontalScrollBar.setVisible (hBarVisible);
  216. verticalScrollBar.setVisible (vBarVisible);
  217. if (contentComp != nullptr)
  218. {
  219. const Point<int> newContentCompPos (viewportPosToCompPos (visibleOrigin));
  220. if (contentComp->getBounds().getPosition() != newContentCompPos)
  221. {
  222. contentComp->setTopLeftPosition (newContentCompPos); // (this will re-entrantly call updateVisibleArea again)
  223. return;
  224. }
  225. }
  226. const Rectangle<int> visibleArea (visibleOrigin.x, visibleOrigin.y,
  227. jmin (contentBounds.getWidth() - visibleOrigin.x, contentArea.getWidth()),
  228. jmin (contentBounds.getHeight() - visibleOrigin.y, contentArea.getHeight()));
  229. if (lastVisibleArea != visibleArea)
  230. {
  231. lastVisibleArea = visibleArea;
  232. visibleAreaChanged (visibleArea);
  233. }
  234. horizontalScrollBar.handleUpdateNowIfNeeded();
  235. verticalScrollBar.handleUpdateNowIfNeeded();
  236. }
  237. //==============================================================================
  238. void Viewport::setSingleStepSizes (const int stepX, const int stepY)
  239. {
  240. if (singleStepX != stepX || singleStepY != stepY)
  241. {
  242. singleStepX = stepX;
  243. singleStepY = stepY;
  244. updateVisibleArea();
  245. }
  246. }
  247. void Viewport::setScrollBarsShown (const bool showVerticalScrollbarIfNeeded,
  248. const bool showHorizontalScrollbarIfNeeded)
  249. {
  250. if (showVScrollbar != showVerticalScrollbarIfNeeded
  251. || showHScrollbar != showHorizontalScrollbarIfNeeded)
  252. {
  253. showVScrollbar = showVerticalScrollbarIfNeeded;
  254. showHScrollbar = showHorizontalScrollbarIfNeeded;
  255. updateVisibleArea();
  256. }
  257. }
  258. void Viewport::setScrollBarThickness (const int thickness)
  259. {
  260. if (scrollBarThickness != thickness)
  261. {
  262. scrollBarThickness = thickness;
  263. updateVisibleArea();
  264. }
  265. }
  266. int Viewport::getScrollBarThickness() const
  267. {
  268. return scrollBarThickness > 0 ? scrollBarThickness
  269. : getLookAndFeel().getDefaultScrollbarWidth();
  270. }
  271. void Viewport::scrollBarMoved (ScrollBar* scrollBarThatHasMoved, double newRangeStart)
  272. {
  273. const int newRangeStartInt = roundToInt (newRangeStart);
  274. if (scrollBarThatHasMoved == &horizontalScrollBar)
  275. {
  276. setViewPosition (newRangeStartInt, getViewPositionY());
  277. }
  278. else if (scrollBarThatHasMoved == &verticalScrollBar)
  279. {
  280. setViewPosition (getViewPositionX(), newRangeStartInt);
  281. }
  282. }
  283. void Viewport::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
  284. {
  285. if (! useMouseWheelMoveIfNeeded (e, wheel))
  286. Component::mouseWheelMove (e, wheel);
  287. }
  288. bool Viewport::useMouseWheelMoveIfNeeded (const MouseEvent& e, const MouseWheelDetails& wheel)
  289. {
  290. if (! (e.mods.isAltDown() || e.mods.isCtrlDown() || e.mods.isCommandDown()))
  291. {
  292. const bool hasVertBar = verticalScrollBar.isVisible();
  293. const bool hasHorzBar = horizontalScrollBar.isVisible();
  294. if (hasHorzBar || hasVertBar)
  295. {
  296. float wheelIncrementX = wheel.deltaX;
  297. float wheelIncrementY = wheel.deltaY;
  298. if (wheelIncrementX != 0)
  299. {
  300. wheelIncrementX *= 14.0f * singleStepX;
  301. wheelIncrementX = (wheelIncrementX < 0) ? jmin (wheelIncrementX, -1.0f)
  302. : jmax (wheelIncrementX, 1.0f);
  303. }
  304. if (wheelIncrementY != 0)
  305. {
  306. wheelIncrementY *= 14.0f * singleStepY;
  307. wheelIncrementY = (wheelIncrementY < 0) ? jmin (wheelIncrementY, -1.0f)
  308. : jmax (wheelIncrementY, 1.0f);
  309. }
  310. Point<int> pos (getViewPosition());
  311. if (wheelIncrementX != 0 && wheelIncrementY != 0 && hasHorzBar && hasVertBar)
  312. {
  313. pos.setX (pos.x - roundToInt (wheelIncrementX));
  314. pos.setY (pos.y - roundToInt (wheelIncrementY));
  315. }
  316. else if (hasHorzBar && (wheelIncrementX != 0 || e.mods.isShiftDown() || ! hasVertBar))
  317. {
  318. if (wheelIncrementX == 0 && ! hasVertBar)
  319. wheelIncrementX = wheelIncrementY;
  320. pos.setX (pos.x - roundToInt (wheelIncrementX));
  321. }
  322. else if (hasVertBar && wheelIncrementY != 0)
  323. {
  324. pos.setY (pos.y - roundToInt (wheelIncrementY));
  325. }
  326. if (pos != getViewPosition())
  327. {
  328. setViewPosition (pos);
  329. return true;
  330. }
  331. }
  332. }
  333. return false;
  334. }
  335. static bool isUpDownKeyPress (const KeyPress& key)
  336. {
  337. return key == KeyPress::upKey
  338. || key == KeyPress::downKey
  339. || key == KeyPress::pageUpKey
  340. || key == KeyPress::pageDownKey
  341. || key == KeyPress::homeKey
  342. || key == KeyPress::endKey;
  343. }
  344. static bool isLeftRightKeyPress (const KeyPress& key)
  345. {
  346. return key == KeyPress::leftKey
  347. || key == KeyPress::rightKey;
  348. }
  349. bool Viewport::keyPressed (const KeyPress& key)
  350. {
  351. const bool isUpDownKey = isUpDownKeyPress (key);
  352. if (verticalScrollBar.isVisible() && isUpDownKey)
  353. return verticalScrollBar.keyPressed (key);
  354. const bool isLeftRightKey = isLeftRightKeyPress (key);
  355. if (horizontalScrollBar.isVisible() && (isUpDownKey || isLeftRightKey))
  356. return horizontalScrollBar.keyPressed (key);
  357. return false;
  358. }
  359. bool Viewport::respondsToKey (const KeyPress& key)
  360. {
  361. return isUpDownKeyPress (key) || isLeftRightKeyPress (key);
  362. }