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.

586 lines
17KB

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