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.

532 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. TabInfo* const tab = tabs[index];
  271. return tab == nullptr ? nullptr : static_cast <TabBarButton*> (tab->button);
  272. }
  273. int TabbedButtonBar::indexOfTabButton (const TabBarButton* button) const
  274. {
  275. for (int i = tabs.size(); --i >= 0;)
  276. if (tabs.getUnchecked(i)->button == button)
  277. return i;
  278. return -1;
  279. }
  280. void TabbedButtonBar::lookAndFeelChanged()
  281. {
  282. extraTabsButton = nullptr;
  283. resized();
  284. }
  285. void TabbedButtonBar::paint (Graphics& g)
  286. {
  287. getLookAndFeel().drawTabbedButtonBarBackground (*this, g);
  288. }
  289. void TabbedButtonBar::resized()
  290. {
  291. LookAndFeel& lf = getLookAndFeel();
  292. int depth = getWidth();
  293. int length = getHeight();
  294. if (! isVertical())
  295. std::swap (depth, length);
  296. const int overlap = lf.getTabButtonOverlap (depth) + lf.getTabButtonSpaceAroundImage() * 2;
  297. int totalLength = jmax (0, overlap);
  298. int numVisibleButtons = tabs.size();
  299. for (int i = 0; i < tabs.size(); ++i)
  300. {
  301. TabBarButton* const tb = tabs.getUnchecked(i)->button;
  302. totalLength += tb->getBestTabLength (depth) - overlap;
  303. tb->overlapPixels = jmax (0, overlap / 2);
  304. }
  305. double scale = 1.0;
  306. if (totalLength > length)
  307. scale = jmax (minimumScale, length / (double) totalLength);
  308. const bool isTooBig = (int) (totalLength * scale) > length;
  309. int tabsButtonPos = 0;
  310. if (isTooBig)
  311. {
  312. if (extraTabsButton == nullptr)
  313. {
  314. addAndMakeVisible (extraTabsButton = lf.createTabBarExtrasButton());
  315. extraTabsButton->addListener (behindFrontTab);
  316. extraTabsButton->setAlwaysOnTop (true);
  317. extraTabsButton->setTriggeredOnMouseDown (true);
  318. }
  319. const int buttonSize = jmin (proportionOfWidth (0.7f), proportionOfHeight (0.7f));
  320. extraTabsButton->setSize (buttonSize, buttonSize);
  321. if (isVertical())
  322. {
  323. tabsButtonPos = getHeight() - buttonSize / 2 - 1;
  324. extraTabsButton->setCentrePosition (getWidth() / 2, tabsButtonPos);
  325. }
  326. else
  327. {
  328. tabsButtonPos = getWidth() - buttonSize / 2 - 1;
  329. extraTabsButton->setCentrePosition (tabsButtonPos, getHeight() / 2);
  330. }
  331. totalLength = 0;
  332. for (int i = 0; i < tabs.size(); ++i)
  333. {
  334. TabBarButton* const tb = tabs.getUnchecked(i)->button;
  335. const int newLength = totalLength + tb->getBestTabLength (depth);
  336. if (i > 0 && newLength * minimumScale > tabsButtonPos)
  337. {
  338. totalLength += overlap;
  339. break;
  340. }
  341. numVisibleButtons = i + 1;
  342. totalLength = newLength - overlap;
  343. }
  344. scale = jmax (minimumScale, tabsButtonPos / (double) totalLength);
  345. }
  346. else
  347. {
  348. extraTabsButton = nullptr;
  349. }
  350. int pos = 0;
  351. TabBarButton* frontTab = nullptr;
  352. for (int i = 0; i < tabs.size(); ++i)
  353. {
  354. if (TabBarButton* const tb = getTabButton (i))
  355. {
  356. const int bestLength = roundToInt (scale * tb->getBestTabLength (depth));
  357. if (i < numVisibleButtons)
  358. {
  359. if (isVertical())
  360. tb->setBounds (0, pos, getWidth(), bestLength);
  361. else
  362. tb->setBounds (pos, 0, bestLength, getHeight());
  363. tb->toBack();
  364. if (i == currentTabIndex)
  365. frontTab = tb;
  366. tb->setVisible (true);
  367. }
  368. else
  369. {
  370. tb->setVisible (false);
  371. }
  372. pos += bestLength - overlap;
  373. }
  374. }
  375. behindFrontTab->setBounds (getLocalBounds());
  376. if (frontTab != nullptr)
  377. {
  378. frontTab->toFront (false);
  379. behindFrontTab->toBehind (frontTab);
  380. }
  381. }
  382. //==============================================================================
  383. Colour TabbedButtonBar::getTabBackgroundColour (const int tabIndex)
  384. {
  385. TabInfo* const tab = tabs [tabIndex];
  386. return tab == nullptr ? Colours::white : tab->colour;
  387. }
  388. void TabbedButtonBar::setTabBackgroundColour (const int tabIndex, Colour newColour)
  389. {
  390. if (TabInfo* const tab = tabs [tabIndex])
  391. {
  392. if (tab->colour != newColour)
  393. {
  394. tab->colour = newColour;
  395. repaint();
  396. }
  397. }
  398. }
  399. void TabbedButtonBar::extraItemsMenuCallback (int result, TabbedButtonBar* bar)
  400. {
  401. if (bar != nullptr && result > 0)
  402. bar->setCurrentTabIndex (result - 1);
  403. }
  404. void TabbedButtonBar::showExtraItemsMenu()
  405. {
  406. PopupMenu m;
  407. for (int i = 0; i < tabs.size(); ++i)
  408. {
  409. const TabInfo* const tab = tabs.getUnchecked(i);
  410. if (! tab->button->isVisible())
  411. m.addItem (i + 1, tab->name, true, i == currentTabIndex);
  412. }
  413. m.showMenuAsync (PopupMenu::Options().withTargetComponent (extraTabsButton),
  414. ModalCallbackFunction::forComponent (extraItemsMenuCallback, this));
  415. }
  416. //==============================================================================
  417. void TabbedButtonBar::currentTabChanged (const int, const String&)
  418. {
  419. }
  420. void TabbedButtonBar::popupMenuClickOnTab (const int, const String&)
  421. {
  422. }