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.

303 lines
8.6KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. //==============================================================================
  21. struct CustomMenuBarItemHolder : public Component
  22. {
  23. CustomMenuBarItemHolder (const ReferenceCountedObjectPtr<PopupMenu::CustomComponent>& customComponent)
  24. {
  25. setInterceptsMouseClicks (false, true);
  26. update (customComponent);
  27. }
  28. void update (const ReferenceCountedObjectPtr<PopupMenu::CustomComponent>& newComponent)
  29. {
  30. jassert (newComponent != nullptr);
  31. if (newComponent != custom)
  32. {
  33. if (custom != nullptr)
  34. removeChildComponent (custom.get());
  35. custom = newComponent;
  36. addAndMakeVisible (*custom);
  37. resized();
  38. }
  39. }
  40. void resized() override
  41. {
  42. custom->setBounds (getLocalBounds());
  43. }
  44. ReferenceCountedObjectPtr<PopupMenu::CustomComponent> custom;
  45. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomMenuBarItemHolder)
  46. };
  47. //==============================================================================
  48. BurgerMenuComponent::BurgerMenuComponent (MenuBarModel* modelToUse)
  49. {
  50. lookAndFeelChanged();
  51. listBox.addMouseListener (this, true);
  52. setModel (modelToUse);
  53. addAndMakeVisible (listBox);
  54. }
  55. BurgerMenuComponent::~BurgerMenuComponent()
  56. {
  57. if (model != nullptr)
  58. model->removeListener (this);
  59. }
  60. void BurgerMenuComponent::setModel (MenuBarModel* newModel)
  61. {
  62. if (newModel != model)
  63. {
  64. if (model != nullptr)
  65. model->removeListener (this);
  66. model = newModel;
  67. if (model != nullptr)
  68. model->addListener (this);
  69. refresh();
  70. listBox.updateContent();
  71. }
  72. }
  73. MenuBarModel* BurgerMenuComponent::getModel() const noexcept
  74. {
  75. return model;
  76. }
  77. void BurgerMenuComponent::refresh()
  78. {
  79. lastRowClicked = inputSourceIndexOfLastClick = -1;
  80. rows.clear();
  81. if (model != nullptr)
  82. {
  83. auto menuBarNames = model->getMenuBarNames();
  84. for (auto menuIdx = 0; menuIdx < menuBarNames.size(); ++menuIdx)
  85. {
  86. PopupMenu::Item menuItem;
  87. menuItem.text = menuBarNames[menuIdx];
  88. String ignore;
  89. auto menu = model->getMenuForIndex (menuIdx, ignore);
  90. rows.add (Row { true, menuIdx, menuItem });
  91. addMenuBarItemsForMenu (menu, menuIdx);
  92. }
  93. }
  94. }
  95. void BurgerMenuComponent::addMenuBarItemsForMenu (PopupMenu& menu, int menuIdx)
  96. {
  97. for (PopupMenu::MenuItemIterator it (menu); it.next();)
  98. {
  99. auto& item = it.getItem();
  100. if (item.isSeparator)
  101. continue;
  102. if (hasSubMenu (item))
  103. addMenuBarItemsForMenu (*item.subMenu, menuIdx);
  104. else
  105. rows.add (Row {false, menuIdx, it.getItem()});
  106. }
  107. }
  108. int BurgerMenuComponent::getNumRows()
  109. {
  110. return rows.size();
  111. }
  112. void BurgerMenuComponent::paint (Graphics& g)
  113. {
  114. getLookAndFeel().drawPopupMenuBackground (g, getWidth(), getHeight());
  115. }
  116. void BurgerMenuComponent::paintListBoxItem (int rowIndex, Graphics& g, int w, int h, bool highlight)
  117. {
  118. auto& lf = getLookAndFeel();
  119. Rectangle<int> r (w, h);
  120. auto row = (rowIndex < rows.size() ? rows.getReference (rowIndex)
  121. : Row { true, 0, {} });
  122. g.fillAll (findColour (PopupMenu::backgroundColourId));
  123. if (row.isMenuHeader)
  124. {
  125. lf.drawPopupMenuSectionHeader (g, r.reduced (20, 0), row.item.text);
  126. g.setColour (Colours::grey);
  127. g.fillRect (r.withHeight (1));
  128. }
  129. else
  130. {
  131. auto& item = row.item;
  132. auto* colour = item.colour != Colour() ? &item.colour : nullptr;
  133. if (item.customComponent == nullptr)
  134. lf.drawPopupMenuItem (g, r.reduced (20, 0),
  135. item.isSeparator,
  136. item.isEnabled,
  137. highlight,
  138. item.isTicked,
  139. hasSubMenu (item),
  140. item.text,
  141. item.shortcutKeyDescription,
  142. item.image.get(),
  143. colour);
  144. }
  145. }
  146. bool BurgerMenuComponent::hasSubMenu (const PopupMenu::Item& item)
  147. {
  148. return item.subMenu != nullptr && (item.itemID == 0 || item.subMenu->getNumItems() > 0);
  149. }
  150. void BurgerMenuComponent::listBoxItemClicked (int rowIndex, const MouseEvent& e)
  151. {
  152. auto row = rowIndex < rows.size() ? rows.getReference (rowIndex)
  153. : Row { true, 0, {} };
  154. if (! row.isMenuHeader)
  155. {
  156. lastRowClicked = rowIndex;
  157. inputSourceIndexOfLastClick = e.source.getIndex();
  158. }
  159. }
  160. Component* BurgerMenuComponent::refreshComponentForRow (int rowIndex, bool isRowSelected, Component* existing)
  161. {
  162. auto row = rowIndex < rows.size() ? rows.getReference (rowIndex)
  163. : Row { true, 0, {} };
  164. auto hasCustomComponent = (row.item.customComponent != nullptr);
  165. if (existing == nullptr && hasCustomComponent)
  166. return new CustomMenuBarItemHolder (row.item.customComponent);
  167. if (existing != nullptr)
  168. {
  169. auto* componentToUpdate = dynamic_cast<CustomMenuBarItemHolder*> (existing);
  170. jassert (componentToUpdate != nullptr);
  171. if (hasCustomComponent && componentToUpdate != nullptr)
  172. {
  173. row.item.customComponent->setHighlighted (isRowSelected);
  174. componentToUpdate->update (row.item.customComponent);
  175. }
  176. else
  177. {
  178. delete existing;
  179. existing = nullptr;
  180. }
  181. }
  182. return existing;
  183. }
  184. void BurgerMenuComponent::resized()
  185. {
  186. listBox.setBounds (getLocalBounds());
  187. }
  188. void BurgerMenuComponent::menuBarItemsChanged (MenuBarModel* menuBarModel)
  189. {
  190. setModel (menuBarModel);
  191. }
  192. void BurgerMenuComponent::menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo&)
  193. {
  194. }
  195. void BurgerMenuComponent::mouseUp (const MouseEvent& event)
  196. {
  197. auto rowIndex = listBox.getSelectedRow();
  198. if (rowIndex == lastRowClicked && rowIndex < rows.size()
  199. && event.source.getIndex() == inputSourceIndexOfLastClick)
  200. {
  201. auto& row = rows.getReference (rowIndex);
  202. if (! row.isMenuHeader)
  203. {
  204. listBox.selectRow (-1);
  205. lastRowClicked = -1;
  206. inputSourceIndexOfLastClick = -1;
  207. topLevelIndexClicked = row.topLevelMenuIndex;
  208. auto& item = row.item;
  209. if (auto* managerOfChosenCommand = item.commandManager)
  210. {
  211. ApplicationCommandTarget::InvocationInfo info (item.itemID);
  212. info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu;
  213. managerOfChosenCommand->invoke (info, true);
  214. }
  215. postCommandMessage (item.itemID);
  216. }
  217. }
  218. }
  219. void BurgerMenuComponent::handleCommandMessage (int commandID)
  220. {
  221. if (model != nullptr)
  222. {
  223. model->menuItemSelected (commandID, topLevelIndexClicked);
  224. topLevelIndexClicked = -1;
  225. refresh();
  226. listBox.updateContent();
  227. }
  228. }
  229. void BurgerMenuComponent::lookAndFeelChanged()
  230. {
  231. listBox.setRowHeight (roundToInt (getLookAndFeel().getPopupMenuFont().getHeight() * 2.0f));
  232. }
  233. //==============================================================================
  234. std::unique_ptr<AccessibilityHandler> BurgerMenuComponent::createAccessibilityHandler()
  235. {
  236. return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::menuBar);
  237. }
  238. } // namespace juce