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.

575 lines
19KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI 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) : Component (name)
  18. {
  19. // content holder is used to clip the contents so they don't overlap the scrollbars
  20. addAndMakeVisible (contentHolder);
  21. contentHolder.setInterceptsMouseClicks (false, true);
  22. scrollBarThickness = getLookAndFeel().getDefaultScrollbarWidth();
  23. addChildComponent (verticalScrollBar);
  24. addChildComponent (horizontalScrollBar);
  25. verticalScrollBar.addListener (this);
  26. horizontalScrollBar.addListener (this);
  27. setInterceptsMouseClicks (false, true);
  28. setWantsKeyboardFocus (true);
  29. }
  30. Viewport::~Viewport()
  31. {
  32. setScrollOnDragEnabled (false);
  33. deleteOrRemoveContentComp();
  34. }
  35. //==============================================================================
  36. void Viewport::visibleAreaChanged (const Rectangle<int>&) {}
  37. void Viewport::viewedComponentChanged (Component*) {}
  38. //==============================================================================
  39. void Viewport::deleteOrRemoveContentComp()
  40. {
  41. if (contentComp != nullptr)
  42. {
  43. contentComp->removeComponentListener (this);
  44. if (deleteContent)
  45. {
  46. // This sets the content comp to a null pointer before deleting the old one, in case
  47. // anything tries to use the old one while it's in mid-deletion..
  48. ScopedPointer<Component> oldCompDeleter (contentComp);
  49. contentComp = nullptr;
  50. }
  51. else
  52. {
  53. contentHolder.removeChildComponent (contentComp);
  54. contentComp = nullptr;
  55. }
  56. }
  57. }
  58. void Viewport::setViewedComponent (Component* const newViewedComponent, const bool deleteComponentWhenNoLongerNeeded)
  59. {
  60. if (contentComp.get() != newViewedComponent)
  61. {
  62. deleteOrRemoveContentComp();
  63. contentComp = newViewedComponent;
  64. deleteContent = deleteComponentWhenNoLongerNeeded;
  65. if (contentComp != nullptr)
  66. {
  67. contentHolder.addAndMakeVisible (contentComp);
  68. setViewPosition (Point<int>());
  69. contentComp->addComponentListener (this);
  70. }
  71. viewedComponentChanged (contentComp);
  72. updateVisibleArea();
  73. }
  74. }
  75. int Viewport::getMaximumVisibleWidth() const { return contentHolder.getWidth(); }
  76. int Viewport::getMaximumVisibleHeight() const { return contentHolder.getHeight(); }
  77. bool Viewport::canScrollVertically() const noexcept { return contentComp->getY() < 0 || contentComp->getBottom() > getHeight(); }
  78. bool Viewport::canScrollHorizontally() const noexcept { return contentComp->getX() < 0 || contentComp->getRight() > getWidth(); }
  79. Point<int> Viewport::viewportPosToCompPos (Point<int> pos) const
  80. {
  81. jassert (contentComp != nullptr);
  82. auto contentBounds = contentHolder.getLocalArea (contentComp, contentComp->getLocalBounds());
  83. Point<int> p (jmax (jmin (0, contentHolder.getWidth() - contentBounds.getWidth()), jmin (0, -(pos.x))),
  84. jmax (jmin (0, contentHolder.getHeight() - contentBounds.getHeight()), jmin (0, -(pos.y))));
  85. return p.transformedBy (contentComp->getTransform().inverted());
  86. }
  87. void Viewport::setViewPosition (const int xPixelsOffset, const int yPixelsOffset)
  88. {
  89. setViewPosition ({ xPixelsOffset, yPixelsOffset });
  90. }
  91. void Viewport::setViewPosition (Point<int> newPosition)
  92. {
  93. if (contentComp != nullptr)
  94. contentComp->setTopLeftPosition (viewportPosToCompPos (newPosition));
  95. }
  96. void Viewport::setViewPositionProportionately (const double x, const double y)
  97. {
  98. if (contentComp != nullptr)
  99. setViewPosition (jmax (0, roundToInt (x * (contentComp->getWidth() - getWidth()))),
  100. jmax (0, roundToInt (y * (contentComp->getHeight() - getHeight()))));
  101. }
  102. bool Viewport::autoScroll (const int mouseX, const int mouseY, const int activeBorderThickness, const int maximumSpeed)
  103. {
  104. if (contentComp != nullptr)
  105. {
  106. int dx = 0, dy = 0;
  107. if (horizontalScrollBar.isVisible() || canScrollHorizontally())
  108. {
  109. if (mouseX < activeBorderThickness)
  110. dx = activeBorderThickness - mouseX;
  111. else if (mouseX >= contentHolder.getWidth() - activeBorderThickness)
  112. dx = (contentHolder.getWidth() - activeBorderThickness) - mouseX;
  113. if (dx < 0)
  114. dx = jmax (dx, -maximumSpeed, contentHolder.getWidth() - contentComp->getRight());
  115. else
  116. dx = jmin (dx, maximumSpeed, -contentComp->getX());
  117. }
  118. if (verticalScrollBar.isVisible() || canScrollVertically())
  119. {
  120. if (mouseY < activeBorderThickness)
  121. dy = activeBorderThickness - mouseY;
  122. else if (mouseY >= contentHolder.getHeight() - activeBorderThickness)
  123. dy = (contentHolder.getHeight() - activeBorderThickness) - mouseY;
  124. if (dy < 0)
  125. dy = jmax (dy, -maximumSpeed, contentHolder.getHeight() - contentComp->getBottom());
  126. else
  127. dy = jmin (dy, maximumSpeed, -contentComp->getY());
  128. }
  129. if (dx != 0 || dy != 0)
  130. {
  131. contentComp->setTopLeftPosition (contentComp->getX() + dx,
  132. contentComp->getY() + dy);
  133. return true;
  134. }
  135. }
  136. return false;
  137. }
  138. void Viewport::componentMovedOrResized (Component&, bool, bool)
  139. {
  140. updateVisibleArea();
  141. }
  142. //==============================================================================
  143. typedef AnimatedPosition<AnimatedPositionBehaviours::ContinuousWithMomentum> ViewportDragPosition;
  144. struct Viewport::DragToScrollListener : private MouseListener,
  145. private ViewportDragPosition::Listener
  146. {
  147. DragToScrollListener (Viewport& v) : viewport (v)
  148. {
  149. viewport.contentHolder.addMouseListener (this, true);
  150. offsetX.addListener (this);
  151. offsetY.addListener (this);
  152. }
  153. ~DragToScrollListener()
  154. {
  155. viewport.contentHolder.removeMouseListener (this);
  156. }
  157. void positionChanged (ViewportDragPosition&, double) override
  158. {
  159. viewport.setViewPosition (originalViewPos - Point<int> ((int) offsetX.getPosition(),
  160. (int) offsetY.getPosition()));
  161. }
  162. void mouseDown (const MouseEvent& e) override
  163. {
  164. if (doesMouseEventComponentBlockViewportDrag (e.eventComponent))
  165. isViewportDragBlocked = true;
  166. ++numTouches;
  167. }
  168. void mouseDrag (const MouseEvent& e) override
  169. {
  170. if (numTouches == 1 && ! isViewportDragBlocked)
  171. {
  172. Point<float> totalOffset = e.getOffsetFromDragStart().toFloat();
  173. if (! isDragging && totalOffset.getDistanceFromOrigin() > 8.0f)
  174. {
  175. isDragging = true;
  176. originalViewPos = viewport.getViewPosition();
  177. offsetX.setPosition (0.0);
  178. offsetX.beginDrag();
  179. offsetY.setPosition (0.0);
  180. offsetY.beginDrag();
  181. }
  182. if (isDragging)
  183. {
  184. offsetX.drag (totalOffset.x);
  185. offsetY.drag (totalOffset.y);
  186. }
  187. }
  188. }
  189. void mouseUp (const MouseEvent& e) override
  190. {
  191. if (doesMouseEventComponentBlockViewportDrag (e.eventComponent))
  192. isViewportDragBlocked = false;
  193. if (--numTouches <= 0)
  194. {
  195. offsetX.endDrag();
  196. offsetY.endDrag();
  197. isDragging = false;
  198. numTouches = 0;
  199. }
  200. }
  201. bool doesMouseEventComponentBlockViewportDrag (const Component* eventComp)
  202. {
  203. for (auto c = eventComp; c != nullptr && c != &viewport; c = c->getParentComponent())
  204. if (c->getViewportIgnoreDragFlag())
  205. return true;
  206. return false;
  207. }
  208. Viewport& viewport;
  209. ViewportDragPosition offsetX, offsetY;
  210. Point<int> originalViewPos;
  211. int numTouches = 0;
  212. bool isDragging = false;
  213. bool isViewportDragBlocked = false;
  214. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DragToScrollListener)
  215. };
  216. void Viewport::setScrollOnDragEnabled (bool shouldScrollOnDrag)
  217. {
  218. if (isScrollOnDragEnabled() != shouldScrollOnDrag)
  219. {
  220. if (shouldScrollOnDrag)
  221. dragToScrollListener = new DragToScrollListener (*this);
  222. else
  223. dragToScrollListener = nullptr;
  224. }
  225. }
  226. bool Viewport::isScrollOnDragEnabled() const noexcept
  227. {
  228. return dragToScrollListener != nullptr;
  229. }
  230. bool Viewport::isCurrentlyScrollingOnDrag() const noexcept
  231. {
  232. return dragToScrollListener != nullptr && dragToScrollListener->isDragging;
  233. }
  234. //==============================================================================
  235. void Viewport::lookAndFeelChanged()
  236. {
  237. if (! customScrollBarThickness)
  238. {
  239. scrollBarThickness = getLookAndFeel().getDefaultScrollbarWidth();
  240. resized();
  241. }
  242. }
  243. void Viewport::resized()
  244. {
  245. updateVisibleArea();
  246. }
  247. //==============================================================================
  248. void Viewport::updateVisibleArea()
  249. {
  250. auto scrollbarWidth = getScrollBarThickness();
  251. const bool canShowAnyBars = getWidth() > scrollbarWidth && getHeight() > scrollbarWidth;
  252. const bool canShowHBar = showHScrollbar && canShowAnyBars;
  253. const bool canShowVBar = showVScrollbar && canShowAnyBars;
  254. bool hBarVisible = false, vBarVisible = false;
  255. Rectangle<int> contentArea;
  256. for (int i = 3; --i >= 0;)
  257. {
  258. hBarVisible = canShowHBar && ! horizontalScrollBar.autoHides();
  259. vBarVisible = canShowVBar && ! verticalScrollBar.autoHides();
  260. contentArea = getLocalBounds();
  261. if (contentComp != nullptr && ! contentArea.contains (contentComp->getBounds()))
  262. {
  263. hBarVisible = canShowHBar && (hBarVisible || contentComp->getX() < 0 || contentComp->getRight() > contentArea.getWidth());
  264. vBarVisible = canShowVBar && (vBarVisible || contentComp->getY() < 0 || contentComp->getBottom() > contentArea.getHeight());
  265. if (vBarVisible)
  266. contentArea.setWidth (getWidth() - scrollbarWidth);
  267. if (hBarVisible)
  268. contentArea.setHeight (getHeight() - scrollbarWidth);
  269. if (! contentArea.contains (contentComp->getBounds()))
  270. {
  271. hBarVisible = canShowHBar && (hBarVisible || contentComp->getRight() > contentArea.getWidth());
  272. vBarVisible = canShowVBar && (vBarVisible || contentComp->getBottom() > contentArea.getHeight());
  273. }
  274. }
  275. if (vBarVisible) contentArea.setWidth (getWidth() - scrollbarWidth);
  276. if (hBarVisible) contentArea.setHeight (getHeight() - scrollbarWidth);
  277. if (contentComp == nullptr)
  278. {
  279. contentHolder.setBounds (contentArea);
  280. break;
  281. }
  282. auto oldContentBounds = contentComp->getBounds();
  283. contentHolder.setBounds (contentArea);
  284. // If the content has changed its size, that might affect our scrollbars, so go round again and re-caclulate..
  285. if (oldContentBounds == contentComp->getBounds())
  286. break;
  287. }
  288. Rectangle<int> contentBounds;
  289. if (contentComp != nullptr)
  290. contentBounds = contentHolder.getLocalArea (contentComp, contentComp->getLocalBounds());
  291. auto visibleOrigin = -contentBounds.getPosition();
  292. horizontalScrollBar.setBounds (0, contentArea.getHeight(), contentArea.getWidth(), scrollbarWidth);
  293. horizontalScrollBar.setRangeLimits (0.0, contentBounds.getWidth());
  294. horizontalScrollBar.setCurrentRange (visibleOrigin.x, contentArea.getWidth());
  295. horizontalScrollBar.setSingleStepSize (singleStepX);
  296. horizontalScrollBar.cancelPendingUpdate();
  297. if (canShowHBar && ! hBarVisible)
  298. visibleOrigin.setX (0);
  299. verticalScrollBar.setBounds (contentArea.getWidth(), 0, scrollbarWidth, contentArea.getHeight());
  300. verticalScrollBar.setRangeLimits (0.0, contentBounds.getHeight());
  301. verticalScrollBar.setCurrentRange (visibleOrigin.y, contentArea.getHeight());
  302. verticalScrollBar.setSingleStepSize (singleStepY);
  303. verticalScrollBar.cancelPendingUpdate();
  304. if (canShowVBar && ! vBarVisible)
  305. visibleOrigin.setY (0);
  306. // Force the visibility *after* setting the ranges to avoid flicker caused by edge conditions in the numbers.
  307. horizontalScrollBar.setVisible (hBarVisible);
  308. verticalScrollBar.setVisible (vBarVisible);
  309. if (contentComp != nullptr)
  310. {
  311. auto newContentCompPos = viewportPosToCompPos (visibleOrigin);
  312. if (contentComp->getBounds().getPosition() != newContentCompPos)
  313. {
  314. contentComp->setTopLeftPosition (newContentCompPos); // (this will re-entrantly call updateVisibleArea again)
  315. return;
  316. }
  317. }
  318. const Rectangle<int> visibleArea (visibleOrigin.x, visibleOrigin.y,
  319. jmin (contentBounds.getWidth() - visibleOrigin.x, contentArea.getWidth()),
  320. jmin (contentBounds.getHeight() - visibleOrigin.y, contentArea.getHeight()));
  321. if (lastVisibleArea != visibleArea)
  322. {
  323. lastVisibleArea = visibleArea;
  324. visibleAreaChanged (visibleArea);
  325. }
  326. horizontalScrollBar.handleUpdateNowIfNeeded();
  327. verticalScrollBar.handleUpdateNowIfNeeded();
  328. }
  329. //==============================================================================
  330. void Viewport::setSingleStepSizes (const int stepX, const int stepY)
  331. {
  332. if (singleStepX != stepX || singleStepY != stepY)
  333. {
  334. singleStepX = stepX;
  335. singleStepY = stepY;
  336. updateVisibleArea();
  337. }
  338. }
  339. void Viewport::setScrollBarsShown (const bool showVerticalScrollbarIfNeeded,
  340. const bool showHorizontalScrollbarIfNeeded,
  341. const bool allowVerticalScrollingWithoutScrollbar,
  342. const bool allowHorizontalScrollingWithoutScrollbar)
  343. {
  344. allowScrollingWithoutScrollbarV = allowVerticalScrollingWithoutScrollbar;
  345. allowScrollingWithoutScrollbarH = allowHorizontalScrollingWithoutScrollbar;
  346. if (showVScrollbar != showVerticalScrollbarIfNeeded
  347. || showHScrollbar != showHorizontalScrollbarIfNeeded)
  348. {
  349. showVScrollbar = showVerticalScrollbarIfNeeded;
  350. showHScrollbar = showHorizontalScrollbarIfNeeded;
  351. updateVisibleArea();
  352. }
  353. }
  354. void Viewport::setScrollBarThickness (const int thickness)
  355. {
  356. int newThickness;
  357. // To stay compatible with the previous code: use the
  358. // default thickness if thickness parameter is zero
  359. // or negative
  360. if (thickness <= 0)
  361. {
  362. customScrollBarThickness = false;
  363. newThickness = getLookAndFeel().getDefaultScrollbarWidth();
  364. }
  365. else
  366. {
  367. customScrollBarThickness = true;
  368. newThickness = thickness;
  369. }
  370. if (scrollBarThickness != newThickness)
  371. {
  372. scrollBarThickness = newThickness;
  373. updateVisibleArea();
  374. }
  375. }
  376. int Viewport::getScrollBarThickness() const
  377. {
  378. return scrollBarThickness;
  379. }
  380. void Viewport::scrollBarMoved (ScrollBar* scrollBarThatHasMoved, double newRangeStart)
  381. {
  382. const int newRangeStartInt = roundToInt (newRangeStart);
  383. if (scrollBarThatHasMoved == &horizontalScrollBar)
  384. {
  385. setViewPosition (newRangeStartInt, getViewPositionY());
  386. }
  387. else if (scrollBarThatHasMoved == &verticalScrollBar)
  388. {
  389. setViewPosition (getViewPositionX(), newRangeStartInt);
  390. }
  391. }
  392. void Viewport::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
  393. {
  394. if (! useMouseWheelMoveIfNeeded (e, wheel))
  395. Component::mouseWheelMove (e, wheel);
  396. }
  397. static int rescaleMouseWheelDistance (float distance, int singleStepSize) noexcept
  398. {
  399. if (distance == 0.0f)
  400. return 0;
  401. distance *= 14.0f * singleStepSize;
  402. return roundToInt (distance < 0 ? jmin (distance, -1.0f)
  403. : jmax (distance, 1.0f));
  404. }
  405. bool Viewport::useMouseWheelMoveIfNeeded (const MouseEvent& e, const MouseWheelDetails& wheel)
  406. {
  407. if (! (e.mods.isAltDown() || e.mods.isCtrlDown() || e.mods.isCommandDown()))
  408. {
  409. const bool canScrollVert = (allowScrollingWithoutScrollbarV || verticalScrollBar.isVisible());
  410. const bool canScrollHorz = (allowScrollingWithoutScrollbarH || horizontalScrollBar.isVisible());
  411. if (canScrollHorz || canScrollVert)
  412. {
  413. auto deltaX = rescaleMouseWheelDistance (wheel.deltaX, singleStepX);
  414. auto deltaY = rescaleMouseWheelDistance (wheel.deltaY, singleStepY);
  415. auto pos = getViewPosition();
  416. if (deltaX != 0 && deltaY != 0 && canScrollHorz && canScrollVert)
  417. {
  418. pos.x -= deltaX;
  419. pos.y -= deltaY;
  420. }
  421. else if (canScrollHorz && (deltaX != 0 || e.mods.isShiftDown() || ! canScrollVert))
  422. {
  423. pos.x -= deltaX != 0 ? deltaX : deltaY;
  424. }
  425. else if (canScrollVert && deltaY != 0)
  426. {
  427. pos.y -= deltaY;
  428. }
  429. if (pos != getViewPosition())
  430. {
  431. setViewPosition (pos);
  432. return true;
  433. }
  434. }
  435. }
  436. return false;
  437. }
  438. static bool isUpDownKeyPress (const KeyPress& key)
  439. {
  440. return key == KeyPress::upKey
  441. || key == KeyPress::downKey
  442. || key == KeyPress::pageUpKey
  443. || key == KeyPress::pageDownKey
  444. || key == KeyPress::homeKey
  445. || key == KeyPress::endKey;
  446. }
  447. static bool isLeftRightKeyPress (const KeyPress& key)
  448. {
  449. return key == KeyPress::leftKey
  450. || key == KeyPress::rightKey;
  451. }
  452. bool Viewport::keyPressed (const KeyPress& key)
  453. {
  454. const bool isUpDownKey = isUpDownKeyPress (key);
  455. if (verticalScrollBar.isVisible() && isUpDownKey)
  456. return verticalScrollBar.keyPressed (key);
  457. const bool isLeftRightKey = isLeftRightKeyPress (key);
  458. if (horizontalScrollBar.isVisible() && (isUpDownKey || isLeftRightKey))
  459. return horizontalScrollBar.keyPressed (key);
  460. return false;
  461. }
  462. bool Viewport::respondsToKey (const KeyPress& key)
  463. {
  464. return isUpDownKeyPress (key) || isLeftRightKeyPress (key);
  465. }