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.

588 lines
17KB

  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. TabBarButton::TabBarButton (const String& name, TabbedButtonBar& owner_)
  20. : Button (name), owner (owner_), overlapPixels (0), extraCompPlacement (afterText)
  21. {
  22. setWantsKeyboardFocus (false);
  23. }
  24. TabBarButton::~TabBarButton() {}
  25. int TabBarButton::getIndex() const { return owner.indexOfTabButton (this); }
  26. Colour TabBarButton::getTabBackgroundColour() const { return owner.getTabBackgroundColour (getIndex()); }
  27. bool TabBarButton::isFrontTab() const { return getToggleState(); }
  28. void TabBarButton::paintButton (Graphics& g, const bool isMouseOverButton, const bool isButtonDown)
  29. {
  30. getLookAndFeel().drawTabButton (*this, g, isMouseOverButton, isButtonDown);
  31. }
  32. void TabBarButton::clicked (const ModifierKeys& mods)
  33. {
  34. if (mods.isPopupMenu())
  35. owner.popupMenuClickOnTab (getIndex(), getButtonText());
  36. else
  37. owner.setCurrentTabIndex (getIndex());
  38. }
  39. bool TabBarButton::hitTest (int mx, int my)
  40. {
  41. const Rectangle<int> area (getActiveArea());
  42. if (owner.isVertical())
  43. {
  44. if (isPositiveAndBelow (mx, getWidth())
  45. && my >= area.getY() + overlapPixels && my < area.getBottom() - overlapPixels)
  46. return true;
  47. }
  48. else
  49. {
  50. if (isPositiveAndBelow (my, getHeight())
  51. && mx >= area.getX() + overlapPixels && mx < area.getRight() - overlapPixels)
  52. return true;
  53. }
  54. Path p;
  55. getLookAndFeel().createTabButtonShape (*this, p, false, false);
  56. return p.contains ((float) (mx - area.getX()),
  57. (float) (my - area.getY()));
  58. }
  59. int TabBarButton::getBestTabLength (const int depth)
  60. {
  61. return getLookAndFeel().getTabButtonBestWidth (*this, depth);
  62. }
  63. void TabBarButton::calcAreas (Rectangle<int>& extraComp, Rectangle<int>& textArea) const
  64. {
  65. LookAndFeel& lf = getLookAndFeel();
  66. textArea = getActiveArea();
  67. const int depth = owner.isVertical() ? textArea.getWidth() : textArea.getHeight();
  68. const int overlap = lf.getTabButtonOverlap (depth);
  69. if (overlap > 0)
  70. {
  71. if (owner.isVertical())
  72. textArea.reduce (0, overlap);
  73. else
  74. textArea.reduce (overlap, 0);
  75. }
  76. if (extraComponent != nullptr)
  77. {
  78. extraComp = lf.getTabButtonExtraComponentBounds (*this, textArea, *extraComponent);
  79. const TabbedButtonBar::Orientation orientation = owner.getOrientation();
  80. if (orientation == TabbedButtonBar::TabsAtLeft || orientation == TabbedButtonBar::TabsAtRight)
  81. {
  82. if (extraComp.getCentreY() > textArea.getCentreY())
  83. textArea.setBottom (jmin (textArea.getBottom(), extraComp.getY()));
  84. else
  85. textArea.setTop (jmax (textArea.getY(), extraComp.getBottom()));
  86. }
  87. else
  88. {
  89. if (extraComp.getCentreX() > textArea.getCentreX())
  90. textArea.setRight (jmin (textArea.getRight(), extraComp.getX()));
  91. else
  92. textArea.setLeft (jmax (textArea.getX(), extraComp.getRight()));
  93. }
  94. }
  95. }
  96. Rectangle<int> TabBarButton::getTextArea() const
  97. {
  98. Rectangle<int> extraComp, textArea;
  99. calcAreas (extraComp, textArea);
  100. return textArea;
  101. }
  102. Rectangle<int> TabBarButton::getActiveArea() const
  103. {
  104. Rectangle<int> r (getLocalBounds());
  105. const int spaceAroundImage = getLookAndFeel().getTabButtonSpaceAroundImage();
  106. const TabbedButtonBar::Orientation orientation = owner.getOrientation();
  107. if (orientation != TabbedButtonBar::TabsAtLeft) r.removeFromRight (spaceAroundImage);
  108. if (orientation != TabbedButtonBar::TabsAtRight) r.removeFromLeft (spaceAroundImage);
  109. if (orientation != TabbedButtonBar::TabsAtBottom) r.removeFromTop (spaceAroundImage);
  110. if (orientation != TabbedButtonBar::TabsAtTop) r.removeFromBottom (spaceAroundImage);
  111. return r;
  112. }
  113. void TabBarButton::setExtraComponent (Component* comp, ExtraComponentPlacement placement)
  114. {
  115. jassert (extraCompPlacement == beforeText || extraCompPlacement == afterText);
  116. extraCompPlacement = placement;
  117. addAndMakeVisible (extraComponent = comp);
  118. resized();
  119. }
  120. void TabBarButton::childBoundsChanged (Component* c)
  121. {
  122. if (c == extraComponent)
  123. {
  124. owner.resized();
  125. resized();
  126. }
  127. }
  128. void TabBarButton::resized()
  129. {
  130. if (extraComponent != nullptr)
  131. {
  132. Rectangle<int> extraComp, textArea;
  133. calcAreas (extraComp, textArea);
  134. if (! extraComp.isEmpty())
  135. extraComponent->setBounds (extraComp);
  136. }
  137. }
  138. //==============================================================================
  139. class TabbedButtonBar::BehindFrontTabComp : public Component,
  140. public Button::Listener
  141. {
  142. public:
  143. BehindFrontTabComp (TabbedButtonBar& tb) : owner (tb)
  144. {
  145. setInterceptsMouseClicks (false, false);
  146. }
  147. void paint (Graphics& g) override
  148. {
  149. getLookAndFeel().drawTabAreaBehindFrontButton (owner, g, getWidth(), getHeight());
  150. }
  151. void enablementChanged() override
  152. {
  153. repaint();
  154. }
  155. void buttonClicked (Button*) override
  156. {
  157. owner.showExtraItemsMenu();
  158. }
  159. private:
  160. TabbedButtonBar& owner;
  161. JUCE_DECLARE_NON_COPYABLE (BehindFrontTabComp)
  162. };
  163. //==============================================================================
  164. TabbedButtonBar::TabbedButtonBar (const Orientation orientation_)
  165. : orientation (orientation_),
  166. minimumScale (0.7),
  167. currentTabIndex (-1)
  168. {
  169. setInterceptsMouseClicks (false, true);
  170. addAndMakeVisible (behindFrontTab = new BehindFrontTabComp (*this));
  171. setFocusContainer (true);
  172. }
  173. TabbedButtonBar::~TabbedButtonBar()
  174. {
  175. tabs.clear();
  176. extraTabsButton = nullptr;
  177. }
  178. //==============================================================================
  179. void TabbedButtonBar::setOrientation (const Orientation newOrientation)
  180. {
  181. orientation = newOrientation;
  182. for (auto* child : getChildren())
  183. child->resized();
  184. resized();
  185. }
  186. TabBarButton* TabbedButtonBar::createTabButton (const String& name, const int /*index*/)
  187. {
  188. return new TabBarButton (name, *this);
  189. }
  190. void TabbedButtonBar::setMinimumTabScaleFactor (double newMinimumScale)
  191. {
  192. minimumScale = newMinimumScale;
  193. resized();
  194. }
  195. //==============================================================================
  196. void TabbedButtonBar::clearTabs()
  197. {
  198. tabs.clear();
  199. extraTabsButton = nullptr;
  200. setCurrentTabIndex (-1);
  201. }
  202. void TabbedButtonBar::addTab (const String& tabName,
  203. Colour tabBackgroundColour,
  204. int insertIndex)
  205. {
  206. jassert (tabName.isNotEmpty()); // you have to give them all a name..
  207. if (tabName.isNotEmpty())
  208. {
  209. if (! isPositiveAndBelow (insertIndex, tabs.size()))
  210. insertIndex = tabs.size();
  211. TabInfo* const currentTab = tabs [currentTabIndex];
  212. TabInfo* newTab = new TabInfo();
  213. newTab->name = tabName;
  214. newTab->colour = tabBackgroundColour;
  215. newTab->button = createTabButton (tabName, insertIndex);
  216. jassert (newTab->button != nullptr);
  217. tabs.insert (insertIndex, newTab);
  218. currentTabIndex = tabs.indexOf (currentTab);
  219. addAndMakeVisible (newTab->button, insertIndex);
  220. resized();
  221. if (currentTabIndex < 0)
  222. setCurrentTabIndex (0);
  223. }
  224. }
  225. void TabbedButtonBar::setTabName (const int tabIndex, const String& newName)
  226. {
  227. if (TabInfo* const tab = tabs [tabIndex])
  228. {
  229. if (tab->name != newName)
  230. {
  231. tab->name = newName;
  232. tab->button->setButtonText (newName);
  233. resized();
  234. }
  235. }
  236. }
  237. void TabbedButtonBar::removeTab (const int indexToRemove, const bool animate)
  238. {
  239. if (isPositiveAndBelow (indexToRemove, tabs.size()))
  240. {
  241. int oldSelectedIndex = currentTabIndex;
  242. if (indexToRemove == currentTabIndex)
  243. oldSelectedIndex = -1;
  244. else if (indexToRemove < oldSelectedIndex)
  245. --oldSelectedIndex;
  246. tabs.remove (indexToRemove);
  247. setCurrentTabIndex (oldSelectedIndex);
  248. updateTabPositions (animate);
  249. }
  250. }
  251. void TabbedButtonBar::moveTab (const int currentIndex, const int newIndex, const bool animate)
  252. {
  253. TabInfo* const currentTab = tabs [currentTabIndex];
  254. tabs.move (currentIndex, newIndex);
  255. currentTabIndex = tabs.indexOf (currentTab);
  256. updateTabPositions (animate);
  257. }
  258. int TabbedButtonBar::getNumTabs() const
  259. {
  260. return tabs.size();
  261. }
  262. String TabbedButtonBar::getCurrentTabName() const
  263. {
  264. TabInfo* tab = tabs [currentTabIndex];
  265. return tab == nullptr ? String() : tab->name;
  266. }
  267. StringArray TabbedButtonBar::getTabNames() const
  268. {
  269. StringArray names;
  270. for (int i = 0; i < tabs.size(); ++i)
  271. names.add (tabs.getUnchecked(i)->name);
  272. return names;
  273. }
  274. void TabbedButtonBar::setCurrentTabIndex (int newIndex, const bool sendChangeMessage_)
  275. {
  276. if (currentTabIndex != newIndex)
  277. {
  278. if (! isPositiveAndBelow (newIndex, tabs.size()))
  279. newIndex = -1;
  280. currentTabIndex = newIndex;
  281. for (int i = 0; i < tabs.size(); ++i)
  282. {
  283. TabBarButton* tb = tabs.getUnchecked(i)->button;
  284. tb->setToggleState (i == newIndex, dontSendNotification);
  285. }
  286. resized();
  287. if (sendChangeMessage_)
  288. sendChangeMessage();
  289. currentTabChanged (newIndex, getCurrentTabName());
  290. }
  291. }
  292. TabBarButton* TabbedButtonBar::getTabButton (const int index) const
  293. {
  294. if (TabInfo* tab = tabs[index])
  295. return static_cast<TabBarButton*> (tab->button);
  296. return nullptr;
  297. }
  298. int TabbedButtonBar::indexOfTabButton (const TabBarButton* button) const
  299. {
  300. for (int i = tabs.size(); --i >= 0;)
  301. if (tabs.getUnchecked(i)->button == button)
  302. return i;
  303. return -1;
  304. }
  305. Rectangle<int> TabbedButtonBar::getTargetBounds (TabBarButton* button) const
  306. {
  307. if (button == nullptr || indexOfTabButton (button) == -1)
  308. return Rectangle<int>();
  309. ComponentAnimator& animator = Desktop::getInstance().getAnimator();
  310. return animator.isAnimating (button) ? animator.getComponentDestination (button) : button->getBounds();
  311. }
  312. void TabbedButtonBar::lookAndFeelChanged()
  313. {
  314. extraTabsButton = nullptr;
  315. resized();
  316. }
  317. void TabbedButtonBar::paint (Graphics& g)
  318. {
  319. getLookAndFeel().drawTabbedButtonBarBackground (*this, g);
  320. }
  321. void TabbedButtonBar::resized()
  322. {
  323. updateTabPositions (false);
  324. }
  325. //==============================================================================
  326. void TabbedButtonBar::updateTabPositions (bool animate)
  327. {
  328. LookAndFeel& lf = getLookAndFeel();
  329. int depth = getWidth();
  330. int length = getHeight();
  331. if (! isVertical())
  332. std::swap (depth, length);
  333. const int overlap = lf.getTabButtonOverlap (depth) + lf.getTabButtonSpaceAroundImage() * 2;
  334. int totalLength = jmax (0, overlap);
  335. int numVisibleButtons = tabs.size();
  336. for (int i = 0; i < tabs.size(); ++i)
  337. {
  338. TabBarButton* const tb = tabs.getUnchecked(i)->button;
  339. totalLength += tb->getBestTabLength (depth) - overlap;
  340. tb->overlapPixels = jmax (0, overlap / 2);
  341. }
  342. double scale = 1.0;
  343. if (totalLength > length)
  344. scale = jmax (minimumScale, length / (double) totalLength);
  345. const bool isTooBig = (int) (totalLength * scale) > length;
  346. int tabsButtonPos = 0;
  347. if (isTooBig)
  348. {
  349. if (extraTabsButton == nullptr)
  350. {
  351. addAndMakeVisible (extraTabsButton = lf.createTabBarExtrasButton());
  352. extraTabsButton->addListener (behindFrontTab);
  353. extraTabsButton->setAlwaysOnTop (true);
  354. extraTabsButton->setTriggeredOnMouseDown (true);
  355. }
  356. const int buttonSize = jmin (proportionOfWidth (0.7f), proportionOfHeight (0.7f));
  357. extraTabsButton->setSize (buttonSize, buttonSize);
  358. if (isVertical())
  359. {
  360. tabsButtonPos = getHeight() - buttonSize / 2 - 1;
  361. extraTabsButton->setCentrePosition (getWidth() / 2, tabsButtonPos);
  362. }
  363. else
  364. {
  365. tabsButtonPos = getWidth() - buttonSize / 2 - 1;
  366. extraTabsButton->setCentrePosition (tabsButtonPos, getHeight() / 2);
  367. }
  368. totalLength = 0;
  369. for (int i = 0; i < tabs.size(); ++i)
  370. {
  371. TabBarButton* const tb = tabs.getUnchecked(i)->button;
  372. const int newLength = totalLength + tb->getBestTabLength (depth);
  373. if (i > 0 && newLength * minimumScale > tabsButtonPos)
  374. {
  375. totalLength += overlap;
  376. break;
  377. }
  378. numVisibleButtons = i + 1;
  379. totalLength = newLength - overlap;
  380. }
  381. scale = jmax (minimumScale, tabsButtonPos / (double) totalLength);
  382. }
  383. else
  384. {
  385. extraTabsButton = nullptr;
  386. }
  387. int pos = 0;
  388. TabBarButton* frontTab = nullptr;
  389. ComponentAnimator& animator = Desktop::getInstance().getAnimator();
  390. for (int i = 0; i < tabs.size(); ++i)
  391. {
  392. if (TabBarButton* const tb = getTabButton (i))
  393. {
  394. const int bestLength = roundToInt (scale * tb->getBestTabLength (depth));
  395. if (i < numVisibleButtons)
  396. {
  397. const Rectangle<int> newBounds (isVertical() ? Rectangle<int> (0, pos, getWidth(), bestLength)
  398. : Rectangle<int> (pos, 0, bestLength, getHeight()));
  399. if (animate)
  400. {
  401. animator.animateComponent (tb, newBounds, 1.0f, 200, false, 3.0, 0.0);
  402. }
  403. else
  404. {
  405. animator.cancelAnimation (tb, false);
  406. tb->setBounds (newBounds);
  407. }
  408. tb->toBack();
  409. if (i == currentTabIndex)
  410. frontTab = tb;
  411. tb->setVisible (true);
  412. }
  413. else
  414. {
  415. tb->setVisible (false);
  416. }
  417. pos += bestLength - overlap;
  418. }
  419. }
  420. behindFrontTab->setBounds (getLocalBounds());
  421. if (frontTab != nullptr)
  422. {
  423. frontTab->toFront (false);
  424. behindFrontTab->toBehind (frontTab);
  425. }
  426. }
  427. //==============================================================================
  428. Colour TabbedButtonBar::getTabBackgroundColour (const int tabIndex)
  429. {
  430. if (TabInfo* tab = tabs [tabIndex])
  431. return tab->colour;
  432. return Colours::transparentBlack;
  433. }
  434. void TabbedButtonBar::setTabBackgroundColour (const int tabIndex, Colour newColour)
  435. {
  436. if (TabInfo* const tab = tabs [tabIndex])
  437. {
  438. if (tab->colour != newColour)
  439. {
  440. tab->colour = newColour;
  441. repaint();
  442. }
  443. }
  444. }
  445. void TabbedButtonBar::extraItemsMenuCallback (int result, TabbedButtonBar* bar)
  446. {
  447. if (bar != nullptr && result > 0)
  448. bar->setCurrentTabIndex (result - 1);
  449. }
  450. void TabbedButtonBar::showExtraItemsMenu()
  451. {
  452. PopupMenu m;
  453. for (int i = 0; i < tabs.size(); ++i)
  454. {
  455. const TabInfo* const tab = tabs.getUnchecked(i);
  456. if (! tab->button->isVisible())
  457. m.addItem (i + 1, tab->name, true, i == currentTabIndex);
  458. }
  459. m.showMenuAsync (PopupMenu::Options().withTargetComponent (extraTabsButton),
  460. ModalCallbackFunction::forComponent (extraItemsMenuCallback, this));
  461. }
  462. //==============================================================================
  463. void TabbedButtonBar::currentTabChanged (const int, const String&)
  464. {
  465. }
  466. void TabbedButtonBar::popupMenuClickOnTab (const int, const String&)
  467. {
  468. }