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.

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