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.

536 lines
15KB

  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. extraComp = lf.getTabButtonExtraComponentBounds (*this, textArea, *extraComponent);
  76. }
  77. Rectangle<int> TabBarButton::getTextArea() const
  78. {
  79. Rectangle<int> extraComp, textArea;
  80. calcAreas (extraComp, textArea);
  81. return textArea;
  82. }
  83. Rectangle<int> TabBarButton::getActiveArea() const
  84. {
  85. Rectangle<int> r (getLocalBounds());
  86. const int spaceAroundImage = getLookAndFeel().getTabButtonSpaceAroundImage();
  87. const TabbedButtonBar::Orientation orientation = owner.getOrientation();
  88. if (orientation != TabbedButtonBar::TabsAtLeft) r.removeFromRight (spaceAroundImage);
  89. if (orientation != TabbedButtonBar::TabsAtRight) r.removeFromLeft (spaceAroundImage);
  90. if (orientation != TabbedButtonBar::TabsAtBottom) r.removeFromTop (spaceAroundImage);
  91. if (orientation != TabbedButtonBar::TabsAtTop) r.removeFromBottom (spaceAroundImage);
  92. return r;
  93. }
  94. void TabBarButton::setExtraComponent (Component* comp, ExtraComponentPlacement placement)
  95. {
  96. jassert (extraCompPlacement == beforeText || extraCompPlacement == afterText);
  97. extraCompPlacement = placement;
  98. addAndMakeVisible (extraComponent = comp);
  99. resized();
  100. }
  101. void TabBarButton::childBoundsChanged (Component* c)
  102. {
  103. if (c == extraComponent)
  104. {
  105. owner.resized();
  106. resized();
  107. }
  108. }
  109. void TabBarButton::resized()
  110. {
  111. if (extraComponent != nullptr)
  112. {
  113. Rectangle<int> extraComp, textArea;
  114. calcAreas (extraComp, textArea);
  115. if (! extraComp.isEmpty())
  116. extraComponent->setBounds (extraComp);
  117. }
  118. }
  119. //==============================================================================
  120. class TabbedButtonBar::BehindFrontTabComp : public Component,
  121. public ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug)
  122. {
  123. public:
  124. BehindFrontTabComp (TabbedButtonBar& tb) : owner (tb)
  125. {
  126. setInterceptsMouseClicks (false, false);
  127. }
  128. void paint (Graphics& g) override
  129. {
  130. getLookAndFeel().drawTabAreaBehindFrontButton (owner, g, getWidth(), getHeight());
  131. }
  132. void enablementChanged() override
  133. {
  134. repaint();
  135. }
  136. void buttonClicked (Button*) override
  137. {
  138. owner.showExtraItemsMenu();
  139. }
  140. private:
  141. TabbedButtonBar& owner;
  142. JUCE_DECLARE_NON_COPYABLE (BehindFrontTabComp)
  143. };
  144. //==============================================================================
  145. TabbedButtonBar::TabbedButtonBar (const Orientation orientation_)
  146. : orientation (orientation_),
  147. minimumScale (0.7),
  148. currentTabIndex (-1)
  149. {
  150. setInterceptsMouseClicks (false, true);
  151. addAndMakeVisible (behindFrontTab = new BehindFrontTabComp (*this));
  152. setFocusContainer (true);
  153. }
  154. TabbedButtonBar::~TabbedButtonBar()
  155. {
  156. tabs.clear();
  157. extraTabsButton = nullptr;
  158. }
  159. //==============================================================================
  160. void TabbedButtonBar::setOrientation (const Orientation newOrientation)
  161. {
  162. orientation = newOrientation;
  163. for (int i = getNumChildComponents(); --i >= 0;)
  164. getChildComponent (i)->resized();
  165. resized();
  166. }
  167. TabBarButton* TabbedButtonBar::createTabButton (const String& name, const int /*index*/)
  168. {
  169. return new TabBarButton (name, *this);
  170. }
  171. void TabbedButtonBar::setMinimumTabScaleFactor (double newMinimumScale)
  172. {
  173. minimumScale = newMinimumScale;
  174. resized();
  175. }
  176. //==============================================================================
  177. void TabbedButtonBar::clearTabs()
  178. {
  179. tabs.clear();
  180. extraTabsButton = nullptr;
  181. setCurrentTabIndex (-1);
  182. }
  183. void TabbedButtonBar::addTab (const String& tabName,
  184. Colour tabBackgroundColour,
  185. int insertIndex)
  186. {
  187. jassert (tabName.isNotEmpty()); // you have to give them all a name..
  188. if (tabName.isNotEmpty())
  189. {
  190. if (! isPositiveAndBelow (insertIndex, tabs.size()))
  191. insertIndex = tabs.size();
  192. TabInfo* const currentTab = tabs [currentTabIndex];
  193. TabInfo* newTab = new TabInfo();
  194. newTab->name = tabName;
  195. newTab->colour = tabBackgroundColour;
  196. newTab->button = createTabButton (tabName, insertIndex);
  197. jassert (newTab->button != nullptr);
  198. tabs.insert (insertIndex, newTab);
  199. currentTabIndex = tabs.indexOf (currentTab);
  200. addAndMakeVisible (newTab->button, insertIndex);
  201. resized();
  202. if (currentTabIndex < 0)
  203. setCurrentTabIndex (0);
  204. }
  205. }
  206. void TabbedButtonBar::setTabName (const int tabIndex, const String& newName)
  207. {
  208. if (TabInfo* const tab = tabs [tabIndex])
  209. {
  210. if (tab->name != newName)
  211. {
  212. tab->name = newName;
  213. tab->button->setButtonText (newName);
  214. resized();
  215. }
  216. }
  217. }
  218. void TabbedButtonBar::removeTab (const int tabIndex)
  219. {
  220. const int oldIndex = currentTabIndex;
  221. if (tabIndex == currentTabIndex)
  222. setCurrentTabIndex (-1);
  223. tabs.remove (tabIndex);
  224. setCurrentTabIndex (oldIndex);
  225. resized();
  226. }
  227. void TabbedButtonBar::moveTab (const int currentIndex, const int newIndex)
  228. {
  229. TabInfo* const currentTab = tabs [currentTabIndex];
  230. tabs.move (currentIndex, newIndex);
  231. currentTabIndex = tabs.indexOf (currentTab);
  232. resized();
  233. }
  234. int TabbedButtonBar::getNumTabs() const
  235. {
  236. return tabs.size();
  237. }
  238. String TabbedButtonBar::getCurrentTabName() const
  239. {
  240. TabInfo* tab = tabs [currentTabIndex];
  241. return tab == nullptr ? String::empty : tab->name;
  242. }
  243. StringArray TabbedButtonBar::getTabNames() const
  244. {
  245. StringArray names;
  246. for (int i = 0; i < tabs.size(); ++i)
  247. names.add (tabs.getUnchecked(i)->name);
  248. return names;
  249. }
  250. void TabbedButtonBar::setCurrentTabIndex (int newIndex, const bool sendChangeMessage_)
  251. {
  252. if (currentTabIndex != newIndex)
  253. {
  254. if (! isPositiveAndBelow (newIndex, tabs.size()))
  255. newIndex = -1;
  256. currentTabIndex = newIndex;
  257. for (int i = 0; i < tabs.size(); ++i)
  258. {
  259. TabBarButton* tb = tabs.getUnchecked(i)->button;
  260. tb->setToggleState (i == newIndex, dontSendNotification);
  261. }
  262. resized();
  263. if (sendChangeMessage_)
  264. sendChangeMessage();
  265. currentTabChanged (newIndex, getCurrentTabName());
  266. }
  267. }
  268. TabBarButton* TabbedButtonBar::getTabButton (const int index) const
  269. {
  270. if (TabInfo* tab = tabs[index])
  271. return static_cast<TabBarButton*> (tab->button);
  272. return nullptr;
  273. }
  274. int TabbedButtonBar::indexOfTabButton (const TabBarButton* button) const
  275. {
  276. for (int i = tabs.size(); --i >= 0;)
  277. if (tabs.getUnchecked(i)->button == button)
  278. return i;
  279. return -1;
  280. }
  281. void TabbedButtonBar::lookAndFeelChanged()
  282. {
  283. extraTabsButton = nullptr;
  284. resized();
  285. }
  286. void TabbedButtonBar::paint (Graphics& g)
  287. {
  288. getLookAndFeel().drawTabbedButtonBarBackground (*this, g);
  289. }
  290. void TabbedButtonBar::resized()
  291. {
  292. LookAndFeel& lf = getLookAndFeel();
  293. int depth = getWidth();
  294. int length = getHeight();
  295. if (! isVertical())
  296. std::swap (depth, length);
  297. const int overlap = lf.getTabButtonOverlap (depth) + lf.getTabButtonSpaceAroundImage() * 2;
  298. int totalLength = jmax (0, overlap);
  299. int numVisibleButtons = tabs.size();
  300. for (int i = 0; i < tabs.size(); ++i)
  301. {
  302. TabBarButton* const tb = tabs.getUnchecked(i)->button;
  303. totalLength += tb->getBestTabLength (depth) - overlap;
  304. tb->overlapPixels = jmax (0, overlap / 2);
  305. }
  306. double scale = 1.0;
  307. if (totalLength > length)
  308. scale = jmax (minimumScale, length / (double) totalLength);
  309. const bool isTooBig = (int) (totalLength * scale) > length;
  310. int tabsButtonPos = 0;
  311. if (isTooBig)
  312. {
  313. if (extraTabsButton == nullptr)
  314. {
  315. addAndMakeVisible (extraTabsButton = lf.createTabBarExtrasButton());
  316. extraTabsButton->addListener (behindFrontTab);
  317. extraTabsButton->setAlwaysOnTop (true);
  318. extraTabsButton->setTriggeredOnMouseDown (true);
  319. }
  320. const int buttonSize = jmin (proportionOfWidth (0.7f), proportionOfHeight (0.7f));
  321. extraTabsButton->setSize (buttonSize, buttonSize);
  322. if (isVertical())
  323. {
  324. tabsButtonPos = getHeight() - buttonSize / 2 - 1;
  325. extraTabsButton->setCentrePosition (getWidth() / 2, tabsButtonPos);
  326. }
  327. else
  328. {
  329. tabsButtonPos = getWidth() - buttonSize / 2 - 1;
  330. extraTabsButton->setCentrePosition (tabsButtonPos, getHeight() / 2);
  331. }
  332. totalLength = 0;
  333. for (int i = 0; i < tabs.size(); ++i)
  334. {
  335. TabBarButton* const tb = tabs.getUnchecked(i)->button;
  336. const int newLength = totalLength + tb->getBestTabLength (depth);
  337. if (i > 0 && newLength * minimumScale > tabsButtonPos)
  338. {
  339. totalLength += overlap;
  340. break;
  341. }
  342. numVisibleButtons = i + 1;
  343. totalLength = newLength - overlap;
  344. }
  345. scale = jmax (minimumScale, tabsButtonPos / (double) totalLength);
  346. }
  347. else
  348. {
  349. extraTabsButton = nullptr;
  350. }
  351. int pos = 0;
  352. TabBarButton* frontTab = nullptr;
  353. for (int i = 0; i < tabs.size(); ++i)
  354. {
  355. if (TabBarButton* const tb = getTabButton (i))
  356. {
  357. const int bestLength = roundToInt (scale * tb->getBestTabLength (depth));
  358. if (i < numVisibleButtons)
  359. {
  360. if (isVertical())
  361. tb->setBounds (0, pos, getWidth(), bestLength);
  362. else
  363. tb->setBounds (pos, 0, bestLength, getHeight());
  364. tb->toBack();
  365. if (i == currentTabIndex)
  366. frontTab = tb;
  367. tb->setVisible (true);
  368. }
  369. else
  370. {
  371. tb->setVisible (false);
  372. }
  373. pos += bestLength - overlap;
  374. }
  375. }
  376. behindFrontTab->setBounds (getLocalBounds());
  377. if (frontTab != nullptr)
  378. {
  379. frontTab->toFront (false);
  380. behindFrontTab->toBehind (frontTab);
  381. }
  382. }
  383. //==============================================================================
  384. Colour TabbedButtonBar::getTabBackgroundColour (const int tabIndex)
  385. {
  386. if (TabInfo* tab = tabs [tabIndex])
  387. return tab->colour;
  388. return Colours::transparentBlack;
  389. }
  390. void TabbedButtonBar::setTabBackgroundColour (const int tabIndex, Colour newColour)
  391. {
  392. if (TabInfo* const tab = tabs [tabIndex])
  393. {
  394. if (tab->colour != newColour)
  395. {
  396. tab->colour = newColour;
  397. repaint();
  398. }
  399. }
  400. }
  401. void TabbedButtonBar::extraItemsMenuCallback (int result, TabbedButtonBar* bar)
  402. {
  403. if (bar != nullptr && result > 0)
  404. bar->setCurrentTabIndex (result - 1);
  405. }
  406. void TabbedButtonBar::showExtraItemsMenu()
  407. {
  408. PopupMenu m;
  409. for (int i = 0; i < tabs.size(); ++i)
  410. {
  411. const TabInfo* const tab = tabs.getUnchecked(i);
  412. if (! tab->button->isVisible())
  413. m.addItem (i + 1, tab->name, true, i == currentTabIndex);
  414. }
  415. m.showMenuAsync (PopupMenu::Options().withTargetComponent (extraTabsButton),
  416. ModalCallbackFunction::forComponent (extraItemsMenuCallback, this));
  417. }
  418. //==============================================================================
  419. void TabbedButtonBar::currentTabChanged (const int, const String&)
  420. {
  421. }
  422. void TabbedButtonBar::popupMenuClickOnTab (const int, const String&)
  423. {
  424. }