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_TabbedButtonBar.cpp 17KB

10 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  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. 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 tabIndex, const bool animate)
  236. {
  237. const int oldIndex = currentTabIndex;
  238. if (tabIndex == currentTabIndex)
  239. setCurrentTabIndex (-1);
  240. tabs.remove (tabIndex);
  241. setCurrentTabIndex (oldIndex);
  242. updateTabPositions (animate);
  243. }
  244. void TabbedButtonBar::moveTab (const int currentIndex, const int newIndex, const bool animate)
  245. {
  246. TabInfo* const currentTab = tabs [currentTabIndex];
  247. tabs.move (currentIndex, newIndex);
  248. currentTabIndex = tabs.indexOf (currentTab);
  249. updateTabPositions (animate);
  250. }
  251. int TabbedButtonBar::getNumTabs() const
  252. {
  253. return tabs.size();
  254. }
  255. String TabbedButtonBar::getCurrentTabName() const
  256. {
  257. TabInfo* tab = tabs [currentTabIndex];
  258. return tab == nullptr ? String::empty : tab->name;
  259. }
  260. StringArray TabbedButtonBar::getTabNames() const
  261. {
  262. StringArray names;
  263. for (int i = 0; i < tabs.size(); ++i)
  264. names.add (tabs.getUnchecked(i)->name);
  265. return names;
  266. }
  267. void TabbedButtonBar::setCurrentTabIndex (int newIndex, const bool sendChangeMessage_)
  268. {
  269. if (currentTabIndex != newIndex)
  270. {
  271. if (! isPositiveAndBelow (newIndex, tabs.size()))
  272. newIndex = -1;
  273. currentTabIndex = newIndex;
  274. for (int i = 0; i < tabs.size(); ++i)
  275. {
  276. TabBarButton* tb = tabs.getUnchecked(i)->button;
  277. tb->setToggleState (i == newIndex, dontSendNotification);
  278. }
  279. resized();
  280. if (sendChangeMessage_)
  281. sendChangeMessage();
  282. currentTabChanged (newIndex, getCurrentTabName());
  283. }
  284. }
  285. TabBarButton* TabbedButtonBar::getTabButton (const int index) const
  286. {
  287. if (TabInfo* tab = tabs[index])
  288. return static_cast<TabBarButton*> (tab->button);
  289. return nullptr;
  290. }
  291. int TabbedButtonBar::indexOfTabButton (const TabBarButton* button) const
  292. {
  293. for (int i = tabs.size(); --i >= 0;)
  294. if (tabs.getUnchecked(i)->button == button)
  295. return i;
  296. return -1;
  297. }
  298. Rectangle<int> TabbedButtonBar::getTargetBounds (TabBarButton* button) const
  299. {
  300. if (button == nullptr || indexOfTabButton (button) == -1)
  301. return Rectangle<int>();
  302. ComponentAnimator& animator = Desktop::getInstance().getAnimator();
  303. return animator.isAnimating (button) ? animator.getComponentDestination (button) : button->getBounds();
  304. }
  305. void TabbedButtonBar::lookAndFeelChanged()
  306. {
  307. extraTabsButton = nullptr;
  308. resized();
  309. }
  310. void TabbedButtonBar::paint (Graphics& g)
  311. {
  312. getLookAndFeel().drawTabbedButtonBarBackground (*this, g);
  313. }
  314. void TabbedButtonBar::resized()
  315. {
  316. updateTabPositions (false);
  317. }
  318. //==============================================================================
  319. void TabbedButtonBar::updateTabPositions (bool animate)
  320. {
  321. LookAndFeel& lf = getLookAndFeel();
  322. int depth = getWidth();
  323. int length = getHeight();
  324. if (! isVertical())
  325. std::swap (depth, length);
  326. const int overlap = lf.getTabButtonOverlap (depth) + lf.getTabButtonSpaceAroundImage() * 2;
  327. int totalLength = jmax (0, overlap);
  328. int numVisibleButtons = tabs.size();
  329. for (int i = 0; i < tabs.size(); ++i)
  330. {
  331. TabBarButton* const tb = tabs.getUnchecked(i)->button;
  332. totalLength += tb->getBestTabLength (depth) - overlap;
  333. tb->overlapPixels = jmax (0, overlap / 2);
  334. }
  335. double scale = 1.0;
  336. if (totalLength > length)
  337. scale = jmax (minimumScale, length / (double) totalLength);
  338. const bool isTooBig = (int) (totalLength * scale) > length;
  339. int tabsButtonPos = 0;
  340. if (isTooBig)
  341. {
  342. if (extraTabsButton == nullptr)
  343. {
  344. addAndMakeVisible (extraTabsButton = lf.createTabBarExtrasButton());
  345. extraTabsButton->addListener (behindFrontTab);
  346. extraTabsButton->setAlwaysOnTop (true);
  347. extraTabsButton->setTriggeredOnMouseDown (true);
  348. }
  349. const int buttonSize = jmin (proportionOfWidth (0.7f), proportionOfHeight (0.7f));
  350. extraTabsButton->setSize (buttonSize, buttonSize);
  351. if (isVertical())
  352. {
  353. tabsButtonPos = getHeight() - buttonSize / 2 - 1;
  354. extraTabsButton->setCentrePosition (getWidth() / 2, tabsButtonPos);
  355. }
  356. else
  357. {
  358. tabsButtonPos = getWidth() - buttonSize / 2 - 1;
  359. extraTabsButton->setCentrePosition (tabsButtonPos, getHeight() / 2);
  360. }
  361. totalLength = 0;
  362. for (int i = 0; i < tabs.size(); ++i)
  363. {
  364. TabBarButton* const tb = tabs.getUnchecked(i)->button;
  365. const int newLength = totalLength + tb->getBestTabLength (depth);
  366. if (i > 0 && newLength * minimumScale > tabsButtonPos)
  367. {
  368. totalLength += overlap;
  369. break;
  370. }
  371. numVisibleButtons = i + 1;
  372. totalLength = newLength - overlap;
  373. }
  374. scale = jmax (minimumScale, tabsButtonPos / (double) totalLength);
  375. }
  376. else
  377. {
  378. extraTabsButton = nullptr;
  379. }
  380. int pos = 0;
  381. TabBarButton* frontTab = nullptr;
  382. ComponentAnimator& animator = Desktop::getInstance().getAnimator();
  383. for (int i = 0; i < tabs.size(); ++i)
  384. {
  385. if (TabBarButton* const tb = getTabButton (i))
  386. {
  387. const int bestLength = roundToInt (scale * tb->getBestTabLength (depth));
  388. if (i < numVisibleButtons)
  389. {
  390. const Rectangle<int> newBounds (isVertical() ? Rectangle<int> (0, pos, getWidth(), bestLength)
  391. : Rectangle<int> (pos, 0, bestLength, getHeight()));
  392. if (animate)
  393. {
  394. animator.animateComponent (tb, newBounds, 1.0f, 200, false, 3.0, 0.0);
  395. }
  396. else
  397. {
  398. animator.cancelAnimation (tb, false);
  399. tb->setBounds (newBounds);
  400. }
  401. tb->toBack();
  402. if (i == currentTabIndex)
  403. frontTab = tb;
  404. tb->setVisible (true);
  405. }
  406. else
  407. {
  408. tb->setVisible (false);
  409. }
  410. pos += bestLength - overlap;
  411. }
  412. }
  413. behindFrontTab->setBounds (getLocalBounds());
  414. if (frontTab != nullptr)
  415. {
  416. frontTab->toFront (false);
  417. behindFrontTab->toBehind (frontTab);
  418. }
  419. }
  420. //==============================================================================
  421. Colour TabbedButtonBar::getTabBackgroundColour (const int tabIndex)
  422. {
  423. if (TabInfo* tab = tabs [tabIndex])
  424. return tab->colour;
  425. return Colours::transparentBlack;
  426. }
  427. void TabbedButtonBar::setTabBackgroundColour (const int tabIndex, Colour newColour)
  428. {
  429. if (TabInfo* const tab = tabs [tabIndex])
  430. {
  431. if (tab->colour != newColour)
  432. {
  433. tab->colour = newColour;
  434. repaint();
  435. }
  436. }
  437. }
  438. void TabbedButtonBar::extraItemsMenuCallback (int result, TabbedButtonBar* bar)
  439. {
  440. if (bar != nullptr && result > 0)
  441. bar->setCurrentTabIndex (result - 1);
  442. }
  443. void TabbedButtonBar::showExtraItemsMenu()
  444. {
  445. PopupMenu m;
  446. for (int i = 0; i < tabs.size(); ++i)
  447. {
  448. const TabInfo* const tab = tabs.getUnchecked(i);
  449. if (! tab->button->isVisible())
  450. m.addItem (i + 1, tab->name, true, i == currentTabIndex);
  451. }
  452. m.showMenuAsync (PopupMenu::Options().withTargetComponent (extraTabsButton),
  453. ModalCallbackFunction::forComponent (extraItemsMenuCallback, this));
  454. }
  455. //==============================================================================
  456. void TabbedButtonBar::currentTabChanged (const int, const String&)
  457. {
  458. }
  459. void TabbedButtonBar::popupMenuClickOnTab (const int, const String&)
  460. {
  461. }