| @@ -1275,6 +1275,72 @@ private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DragAndDropDemo) | |||
| }; | |||
| //============================================================================== | |||
| struct BurgerMenuHeader : public Component, | |||
| private Button::Listener | |||
| { | |||
| BurgerMenuHeader() | |||
| { | |||
| static const unsigned char burgerMenuPathData[] | |||
| = { 110,109,0,0,128,64,0,0,32,65,108,0,0,224,65,0,0,32,65,98,254,212,232,65,0,0,32,65,0,0,240,65,252, | |||
| 169,17,65,0,0,240,65,0,0,0,65,98,0,0,240,65,8,172,220,64,254,212,232,65,0,0,192,64,0,0,224,65,0,0, | |||
| 192,64,108,0,0,128,64,0,0,192,64,98,16,88,57,64,0,0,192,64,0,0,0,64,8,172,220,64,0,0,0,64,0,0,0,65, | |||
| 98,0,0,0,64,252,169,17,65,16,88,57,64,0,0,32,65,0,0,128,64,0,0,32,65,99,109,0,0,224,65,0,0,96,65,108, | |||
| 0,0,128,64,0,0,96,65,98,16,88,57,64,0,0,96,65,0,0,0,64,4,86,110,65,0,0,0,64,0,0,128,65,98,0,0,0,64, | |||
| 254,212,136,65,16,88,57,64,0,0,144,65,0,0,128,64,0,0,144,65,108,0,0,224,65,0,0,144,65,98,254,212,232, | |||
| 65,0,0,144,65,0,0,240,65,254,212,136,65,0,0,240,65,0,0,128,65,98,0,0,240,65,4,86,110,65,254,212,232, | |||
| 65,0,0,96,65,0,0,224,65,0,0,96,65,99,109,0,0,224,65,0,0,176,65,108,0,0,128,64,0,0,176,65,98,16,88,57, | |||
| 64,0,0,176,65,0,0,0,64,2,43,183,65,0,0,0,64,0,0,192,65,98,0,0,0,64,254,212,200,65,16,88,57,64,0,0,208, | |||
| 65,0,0,128,64,0,0,208,65,108,0,0,224,65,0,0,208,65,98,254,212,232,65,0,0,208,65,0,0,240,65,254,212, | |||
| 200,65,0,0,240,65,0,0,192,65,98,0,0,240,65,2,43,183,65,254,212,232,65,0,0,176,65,0,0,224,65,0,0,176, | |||
| 65,99,101,0,0 }; | |||
| Path p; | |||
| p.loadPathFromData (burgerMenuPathData, sizeof (burgerMenuPathData)); | |||
| burgerButton.setShape (p, true, true, false); | |||
| burgerButton.addListener (this); | |||
| addAndMakeVisible (burgerButton); | |||
| } | |||
| ~BurgerMenuHeader() | |||
| { | |||
| MainAppWindow::getSharedSidePanel().showOrHide (false); | |||
| } | |||
| private: | |||
| void paint (Graphics& g) override | |||
| { | |||
| auto titleBarBackgroundColour = getLookAndFeel().findColour (ResizableWindow::backgroundColourId) | |||
| .darker(); | |||
| g.setColour (titleBarBackgroundColour); | |||
| g.fillRect (getLocalBounds()); | |||
| } | |||
| void resized() override | |||
| { | |||
| auto r = getLocalBounds(); | |||
| burgerButton.setBounds (r.removeFromRight (40).withSizeKeepingCentre (20, 20)); | |||
| titleLabel.setFont (Font (getHeight() * 0.5f, Font::plain)); | |||
| titleLabel.setBounds (r); | |||
| } | |||
| void buttonClicked (Button*) override | |||
| { | |||
| auto& panel = MainAppWindow::getSharedSidePanel(); | |||
| panel.showOrHide (! panel.isPanelShowing()); | |||
| } | |||
| Label titleLabel { "titleLabel", "JUCE Demo" }; | |||
| ShapeButton burgerButton { "burgerButton", Colours::lightgrey, Colours::lightgrey, Colours::white }; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BurgerMenuHeader) | |||
| }; | |||
| //============================================================================== | |||
| class MenusDemo : public Component, | |||
| public MenuBarModel, | |||
| @@ -1282,6 +1348,15 @@ class MenusDemo : public Component, | |||
| private Button::Listener | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| enum MenuBarPosition | |||
| { | |||
| window, | |||
| globalMenuBar, | |||
| burger | |||
| }; | |||
| //============================================================================== | |||
| MenusDemo() | |||
| { | |||
| addAndMakeVisible (menuBar = new MenuBarComponent (this)); | |||
| @@ -1290,12 +1365,15 @@ public: | |||
| popupButton.setTriggeredOnMouseDown (true); | |||
| popupButton.addListener (this); | |||
| addAndMakeVisible (popupButton); | |||
| addChildComponent (menuHeader); | |||
| setApplicationCommandManagerToWatch (&MainAppWindow::getApplicationCommandManager()); | |||
| } | |||
| ~MenusDemo() | |||
| { | |||
| MainAppWindow::getSharedSidePanel().setContent (nullptr, false); | |||
| #if JUCE_MAC | |||
| MenuBarModel::setMacMainMenu (nullptr); | |||
| #endif | |||
| @@ -1307,7 +1385,13 @@ public: | |||
| void resized() override | |||
| { | |||
| Rectangle<int> area (getLocalBounds()); | |||
| menuBar->setBounds (area.removeFromTop (LookAndFeel::getDefaultLookAndFeel().getDefaultMenuBarHeight())); | |||
| { | |||
| auto menuBarArea = area.removeFromTop (40); | |||
| menuBar->setBounds (menuBarArea.withHeight (LookAndFeel::getDefaultLookAndFeel().getDefaultMenuBarHeight())); | |||
| menuHeader.setBounds (menuBarArea); | |||
| } | |||
| area.removeFromTop (20); | |||
| area = area.removeFromTop (33); | |||
| @@ -1317,7 +1401,7 @@ public: | |||
| //============================================================================== | |||
| StringArray getMenuBarNames() override | |||
| { | |||
| return { "Demo", "Look-and-feel", "Tabs", "Misc" }; | |||
| return { "Demo", "Look-and-feel", "Menus", "Tabs", "Misc" }; | |||
| } | |||
| PopupMenu getMenuForIndex (int menuIndex, const String& /*menuName*/) override | |||
| @@ -1350,10 +1434,6 @@ public: | |||
| menu.addSeparator(); | |||
| menu.addCommandItem (commandManager, MainAppWindow::useNativeTitleBar); | |||
| #if JUCE_MAC | |||
| menu.addItem (6000, "Use Native Menu Bar"); | |||
| #endif | |||
| #if ! JUCE_LINUX | |||
| menu.addCommandItem (commandManager, MainAppWindow::goToKioskMode); | |||
| #endif | |||
| @@ -1372,6 +1452,14 @@ public: | |||
| } | |||
| } | |||
| else if (menuIndex == 2) | |||
| { | |||
| menu.addItem (6000, "Inside Window", true, menuBarPosition == window); | |||
| #if JUCE_MAC | |||
| menu.addItem (6001, "Global Menu Bar", true, menuBarPosition == globalMenuBar); | |||
| #endif | |||
| menu.addItem (6002, "Burger Menu", true, menuBarPosition == burger); | |||
| } | |||
| else if (menuIndex == 3) | |||
| { | |||
| if (TabbedComponent* tabs = findParentComponentOfClass<TabbedComponent>()) | |||
| { | |||
| @@ -1381,7 +1469,7 @@ public: | |||
| menu.addItem (3003, "Tabs on Right", true, tabs->getOrientation() == TabbedButtonBar::TabsAtRight); | |||
| } | |||
| } | |||
| else if (menuIndex == 3) | |||
| else if (menuIndex == 4) | |||
| { | |||
| return getDummyPopupMenu(); | |||
| } | |||
| @@ -1394,20 +1482,28 @@ public: | |||
| // most of our menu items are invoked automatically as commands, but we can handle the | |||
| // other special cases here.. | |||
| if (menuItemID == 6000) | |||
| if (menuItemID >= 6000 && menuItemID < 7000) | |||
| { | |||
| #if JUCE_MAC | |||
| if (MenuBarModel::getMacMainMenu() != nullptr) | |||
| { | |||
| MenuBarModel::setMacMainMenu (nullptr); | |||
| menuBar->setModel (this); | |||
| } | |||
| else | |||
| auto newPosition = static_cast<MenuBarPosition> (menuItemID - 6000); | |||
| if (newPosition != menuBarPosition) | |||
| { | |||
| menuBar->setModel (nullptr); | |||
| MenuBarModel::setMacMainMenu (this); | |||
| menuBarPosition = newPosition; | |||
| if (menuBarPosition != burger) | |||
| MainAppWindow::getSharedSidePanel().showOrHide (false); | |||
| #if JUCE_MAC | |||
| MenuBarModel::setMacMainMenu (menuBarPosition == globalMenuBar ? this : nullptr); | |||
| #endif | |||
| menuBar->setModel (menuBarPosition == window ? this : nullptr); | |||
| menuBar->setVisible (menuBarPosition == window); | |||
| burgerMenu.setModel (menuBarPosition == burger ? this : nullptr); | |||
| menuHeader.setVisible (menuBarPosition == burger); | |||
| MainAppWindow::getSharedSidePanel().setContent (menuBarPosition == burger ? &burgerMenu : nullptr, false); | |||
| menuItemsChanged(); | |||
| } | |||
| #endif | |||
| } | |||
| else if (menuItemID >= 3000 && menuItemID <= 3003) | |||
| { | |||
| @@ -1431,6 +1527,9 @@ public: | |||
| private: | |||
| TextButton popupButton; | |||
| ScopedPointer<MenuBarComponent> menuBar; | |||
| BurgerMenuComponent burgerMenu; | |||
| BurgerMenuHeader menuHeader; | |||
| MenuBarPosition menuBarPosition = window; | |||
| PopupMenu getDummyPopupMenu() | |||
| { | |||
| @@ -1443,21 +1542,24 @@ private: | |||
| m.addCustomItem (5, new CustomMenuComponent()); | |||
| m.addSeparator(); | |||
| for (int i = 0; i < 8; ++i) | |||
| if (menuBarPosition != burger) | |||
| { | |||
| PopupMenu subMenu; | |||
| for (int s = 0; s < 8; ++s) | |||
| for (int i = 0; i < 8; ++i) | |||
| { | |||
| PopupMenu subSubMenu; | |||
| PopupMenu subMenu; | |||
| for (int item = 0; item < 8; ++item) | |||
| subSubMenu.addItem (1000 + (i * s * item), "Item " + String (item + 1)); | |||
| for (int s = 0; s < 8; ++s) | |||
| { | |||
| PopupMenu subSubMenu; | |||
| subMenu.addSubMenu ("Sub-sub menu " + String (s + 1), subSubMenu); | |||
| } | |||
| for (int item = 0; item < 8; ++item) | |||
| subSubMenu.addItem (1000 + (i * s * item), "Item " + String (item + 1)); | |||
| m.addSubMenu ("Sub menu " + String (i + 1), subMenu); | |||
| subMenu.addSubMenu ("Sub-sub menu " + String (s + 1), subSubMenu); | |||
| } | |||
| m.addSubMenu ("Sub menu " + String (i + 1), subMenu); | |||
| } | |||
| } | |||
| return m; | |||
| @@ -1508,9 +1610,11 @@ private: | |||
| void timerCallback() override | |||
| { | |||
| Random random; | |||
| blobPosition.setBounds ((float) random.nextInt (getWidth()), | |||
| (float) random.nextInt (getHeight()), | |||
| 40.0f, 30.0f); | |||
| if (! getBounds().isEmpty()) | |||
| blobPosition.setBounds ((float) random.nextInt (getWidth()), | |||
| (float) random.nextInt (getHeight()), | |||
| 40.0f, 30.0f); | |||
| repaint(); | |||
| } | |||
| @@ -54,6 +54,11 @@ public: | |||
| mainWindow.reset(); | |||
| } | |||
| void backButtonPressed() override | |||
| { | |||
| MainAppWindow::getSharedSidePanel().showOrHide (false); | |||
| } | |||
| //============================================================================== | |||
| void systemRequestedQuit() override | |||
| { | |||
| @@ -124,6 +124,8 @@ public: | |||
| demoList.getViewport()->setScrollOnDragEnabled (true); | |||
| addAndMakeVisible (demoList); | |||
| addAndMakeVisible (sidePanel); | |||
| sidePanel.setAlwaysOnTop (true); | |||
| } | |||
| ~ContentComponent() | |||
| @@ -246,9 +248,15 @@ public: | |||
| return currentDemo != nullptr && currentDemo->getName().contains ("OpenGL 2D"); | |||
| } | |||
| SidePanel& getSharedSidePanel() | |||
| { | |||
| return sidePanel; | |||
| } | |||
| private: | |||
| ListBox demoList; | |||
| ScopedPointer<Component> currentDemo; | |||
| SidePanel sidePanel {"Menu", 300, false}; | |||
| LookAndFeel_V1 lookAndFeelV1; | |||
| LookAndFeel_V2 lookAndFeelV2; | |||
| @@ -745,6 +753,11 @@ int MainAppWindow::getActiveRenderingEngine() const | |||
| return 0; | |||
| } | |||
| SidePanel& MainAppWindow::getSharedSidePanel() | |||
| { | |||
| return getMainAppWindow()->contentComponent->getSharedSidePanel(); | |||
| } | |||
| Path MainAppWindow::getJUCELogoPath() | |||
| { | |||
| return Drawable::parseSVGPath ( | |||
| @@ -48,6 +48,8 @@ public: | |||
| // (returns a shared AudioDeviceManager object that all the demos can use) | |||
| static AudioDeviceManager& getSharedAudioDeviceManager(); | |||
| static SidePanel& getSharedSidePanel(); | |||
| StringArray getRenderingEngines() const; | |||
| int getActiveRenderingEngine() const; | |||
| void setRenderingEngine (int index); | |||
| @@ -215,6 +215,7 @@ namespace juce | |||
| #include "lookandfeel/juce_LookAndFeel_V3.cpp" | |||
| #include "lookandfeel/juce_LookAndFeel_V4.cpp" | |||
| #include "menus/juce_MenuBarComponent.cpp" | |||
| #include "menus/juce_BurgerMenuComponent.cpp" | |||
| #include "menus/juce_MenuBarModel.cpp" | |||
| #include "menus/juce_PopupMenu.cpp" | |||
| #include "positioning/juce_MarkerList.cpp" | |||
| @@ -248,6 +248,7 @@ namespace juce | |||
| #include "widgets/juce_ToolbarItemComponent.h" | |||
| #include "widgets/juce_ToolbarItemFactory.h" | |||
| #include "widgets/juce_ToolbarItemPalette.h" | |||
| #include "menus/juce_BurgerMenuComponent.h" | |||
| #include "buttons/juce_ToolbarButton.h" | |||
| #include "misc/juce_DropShadower.h" | |||
| #include "misc/juce_JUCESplashScreen.h" | |||
| @@ -58,7 +58,8 @@ public: | |||
| the caller must manage the lifetime of the component | |||
| */ | |||
| SidePanel (StringRef title, int width, bool positionOnLeft, | |||
| Component* contentComponent = nullptr, bool deleteComponentWhenNoLongerNeeded = true); | |||
| Component* contentComponent = nullptr, | |||
| bool deleteComponentWhenNoLongerNeeded = true); | |||
| /** Destructor */ | |||
| ~SidePanel(); | |||
| @@ -156,7 +157,7 @@ private: | |||
| OptionalScopedPointer<Component> contentComponent; | |||
| Label titleLabel; | |||
| ShapeButton dismissButton {"dismissButton", Colours::lightgrey, Colours::lightgrey, Colours::white}; | |||
| ShapeButton dismissButton { "dismissButton", Colours::lightgrey, Colours::lightgrey, Colours::white }; | |||
| Rectangle<int> shadowArea; | |||
| @@ -0,0 +1,294 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| struct CustomMenuBarItemHolder : public Component | |||
| { | |||
| CustomMenuBarItemHolder (const ReferenceCountedObjectPtr<PopupMenu::CustomComponent>& customComponent) | |||
| { | |||
| setInterceptsMouseClicks (false, true); | |||
| update (customComponent); | |||
| } | |||
| void update (const ReferenceCountedObjectPtr<PopupMenu::CustomComponent>& newComponent) | |||
| { | |||
| jassert (newComponent != nullptr); | |||
| if (newComponent != custom) | |||
| { | |||
| if (custom != nullptr) | |||
| removeChildComponent (custom); | |||
| custom = newComponent; | |||
| addAndMakeVisible (custom); | |||
| resized(); | |||
| } | |||
| } | |||
| void resized() override | |||
| { | |||
| custom->setBounds (getLocalBounds()); | |||
| } | |||
| ReferenceCountedObjectPtr<PopupMenu::CustomComponent> custom; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomMenuBarItemHolder) | |||
| }; | |||
| //============================================================================== | |||
| BurgerMenuComponent::BurgerMenuComponent (MenuBarModel* modelToUse) | |||
| { | |||
| auto& lf = getLookAndFeel(); | |||
| listBox.setRowHeight (roundToInt (lf.getPopupMenuFont().getHeight() * 2.0f)); | |||
| listBox.addMouseListener (this, true); | |||
| setModel (modelToUse); | |||
| addAndMakeVisible (listBox); | |||
| } | |||
| BurgerMenuComponent::~BurgerMenuComponent() | |||
| { | |||
| if (model != nullptr) | |||
| model->removeListener (this); | |||
| } | |||
| void BurgerMenuComponent::setModel (MenuBarModel* newModel) | |||
| { | |||
| if (newModel != model) | |||
| { | |||
| if (model != nullptr) | |||
| model->removeListener (this); | |||
| model = newModel; | |||
| if (model != nullptr) | |||
| model->addListener (this); | |||
| refresh(); | |||
| listBox.updateContent(); | |||
| } | |||
| } | |||
| MenuBarModel* BurgerMenuComponent::getModel() const noexcept | |||
| { | |||
| return model; | |||
| } | |||
| void BurgerMenuComponent::refresh() | |||
| { | |||
| lastRowClicked = inputSourceIndexOfLastClick = -1; | |||
| rows.clear(); | |||
| if (model != nullptr) | |||
| { | |||
| auto menuBarNames = model->getMenuBarNames(); | |||
| for (auto menuIdx = 0; menuIdx < menuBarNames.size(); ++menuIdx) | |||
| { | |||
| PopupMenu::Item menuItem; | |||
| menuItem.text = menuBarNames[menuIdx]; | |||
| String ignore; | |||
| auto menu = model->getMenuForIndex (menuIdx, ignore); | |||
| rows.add (Row { true, menuIdx, menuItem }); | |||
| addMenuBarItemsForMenu (menu, menuIdx); | |||
| } | |||
| } | |||
| } | |||
| void BurgerMenuComponent::addMenuBarItemsForMenu (PopupMenu& menu, int menuIdx) | |||
| { | |||
| for (PopupMenu::MenuItemIterator it (menu); it.next();) | |||
| { | |||
| auto& item = it.getItem(); | |||
| if (item.isSeparator) | |||
| continue; | |||
| if (hasSubMenu (item)) | |||
| addMenuBarItemsForMenu (*item.subMenu, menuIdx); | |||
| else | |||
| rows.add (Row {false, menuIdx, it.getItem()}); | |||
| } | |||
| } | |||
| int BurgerMenuComponent::getNumRows() | |||
| { | |||
| return rows.size(); | |||
| } | |||
| void BurgerMenuComponent::paint (Graphics& g) | |||
| { | |||
| getLookAndFeel().drawPopupMenuBackground (g, getWidth(), getHeight()); | |||
| } | |||
| void BurgerMenuComponent::paintListBoxItem (int rowIndex, Graphics& g, int w, int h, bool highlight) | |||
| { | |||
| auto& lf = getLookAndFeel(); | |||
| Rectangle<int> r (w, h); | |||
| auto row = (rowIndex < rows.size() ? rows.getReference (rowIndex) | |||
| : Row { true, 0, {} }); | |||
| g.fillAll (findColour (PopupMenu::backgroundColourId)); | |||
| if (row.isMenuHeader) | |||
| { | |||
| lf.drawPopupMenuSectionHeader (g, r.reduced (20, 0), row.item.text); | |||
| g.setColour (Colours::grey); | |||
| g.fillRect (r.withHeight (1)); | |||
| } | |||
| else | |||
| { | |||
| auto& item = row.item; | |||
| auto* colour = item.colour != Colour() ? &item.colour : nullptr; | |||
| if (item.customComponent == nullptr) | |||
| lf.drawPopupMenuItem (g, r.reduced (20, 0), | |||
| item.isSeparator, | |||
| item.isEnabled, | |||
| highlight, | |||
| item.isTicked, | |||
| hasSubMenu (item), | |||
| item.text, | |||
| item.shortcutKeyDescription, | |||
| item.image, | |||
| colour); | |||
| } | |||
| } | |||
| bool BurgerMenuComponent::hasSubMenu (const PopupMenu::Item& item) | |||
| { | |||
| return item.subMenu != nullptr && (item.itemID == 0 || item.subMenu->getNumItems() > 0); | |||
| } | |||
| void BurgerMenuComponent::listBoxItemClicked (int rowIndex, const MouseEvent& e) | |||
| { | |||
| auto row = rowIndex < rows.size() ? rows.getReference (rowIndex) | |||
| : Row { true, 0, {} }; | |||
| if (! row.isMenuHeader) | |||
| { | |||
| lastRowClicked = rowIndex; | |||
| inputSourceIndexOfLastClick = e.source.getIndex(); | |||
| } | |||
| } | |||
| Component* BurgerMenuComponent::refreshComponentForRow (int rowIndex, bool isRowSelected, Component* existing) | |||
| { | |||
| auto row = rowIndex < rows.size() ? rows.getReference (rowIndex) | |||
| : Row { true, 0, {} }; | |||
| auto hasCustomComponent = (row.item.customComponent != nullptr); | |||
| if (existing == nullptr && hasCustomComponent) | |||
| return new CustomMenuBarItemHolder (row.item.customComponent); | |||
| if (existing != nullptr) | |||
| { | |||
| auto* componentToUpdate = dynamic_cast<CustomMenuBarItemHolder*> (existing); | |||
| jassert (componentToUpdate != nullptr); | |||
| if (hasCustomComponent && componentToUpdate != nullptr) | |||
| { | |||
| row.item.customComponent->setHighlighted (isRowSelected); | |||
| componentToUpdate->update (row.item.customComponent); | |||
| } | |||
| else | |||
| { | |||
| delete existing; | |||
| existing = nullptr; | |||
| } | |||
| } | |||
| return existing; | |||
| } | |||
| void BurgerMenuComponent::resized() | |||
| { | |||
| listBox.setBounds (getLocalBounds()); | |||
| } | |||
| void BurgerMenuComponent::menuBarItemsChanged (MenuBarModel* menuBarModel) | |||
| { | |||
| setModel (menuBarModel); | |||
| } | |||
| void BurgerMenuComponent::menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo&) | |||
| { | |||
| } | |||
| void BurgerMenuComponent::mouseUp (const MouseEvent& event) | |||
| { | |||
| auto rowIndex = listBox.getSelectedRow(); | |||
| if (rowIndex == lastRowClicked && rowIndex < rows.size() | |||
| && event.source.getIndex() == inputSourceIndexOfLastClick) | |||
| { | |||
| auto& row = rows.getReference (rowIndex); | |||
| if (! row.isMenuHeader) | |||
| { | |||
| listBox.selectRow (-1); | |||
| lastRowClicked = -1; | |||
| inputSourceIndexOfLastClick = -1; | |||
| topLevelIndexClicked = row.topLevelMenuIndex; | |||
| auto& item = row.item; | |||
| if (auto* managerOfChosenCommand = item.commandManager) | |||
| { | |||
| ApplicationCommandTarget::InvocationInfo info (item.itemID); | |||
| info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu; | |||
| managerOfChosenCommand->invoke (info, true); | |||
| } | |||
| postCommandMessage (item.itemID); | |||
| } | |||
| } | |||
| } | |||
| void BurgerMenuComponent::handleCommandMessage (int commandID) | |||
| { | |||
| if (model != nullptr) | |||
| { | |||
| model->menuItemSelected (commandID, topLevelIndexClicked); | |||
| topLevelIndexClicked = -1; | |||
| refresh(); | |||
| listBox.updateContent(); | |||
| } | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,106 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| A component which lists all menu items and groups them into categories | |||
| by their respective parent menus. This kind of component is often used | |||
| for so-called "burger" menus in mobile apps. | |||
| @see MenuBarModel | |||
| */ | |||
| //============================================================================== | |||
| class BurgerMenuComponent : public Component, | |||
| private ListBoxModel, | |||
| private MenuBarModel::Listener | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a burger menu component. | |||
| @param model the model object to use to control this burger menu. You can | |||
| set the parameter or pass nullptr into this if you like, | |||
| and set the model later using the setModel() method. | |||
| @see setModel | |||
| */ | |||
| BurgerMenuComponent (MenuBarModel* model = nullptr); | |||
| /** Destructor. */ | |||
| ~BurgerMenuComponent(); | |||
| //============================================================================== | |||
| /** Changes the model object to use to control the burger menu. | |||
| This can be a nullptr, in which case the bar will be empty. This object will not be | |||
| owned by the BurgerMenuComponent so it is up to you to manage its lifetime. | |||
| Don't delete the object that is passed-in while it's still being used by this MenuBar. | |||
| Any submenus in your MenuBarModel will be recursively flattened and added to the | |||
| top-level burger menu section. | |||
| */ | |||
| void setModel (MenuBarModel* newModel); | |||
| /** Returns the current burger menu model being used. */ | |||
| MenuBarModel* getModel() const noexcept; | |||
| private: | |||
| //============================================================================== | |||
| struct Row | |||
| { | |||
| bool isMenuHeader; | |||
| int topLevelMenuIndex; | |||
| PopupMenu::Item item; | |||
| }; | |||
| void refresh(); | |||
| void paint (Graphics&) override; | |||
| int getNumRows() override; | |||
| void paintListBoxItem (int, Graphics&, int, int, bool) override; | |||
| void listBoxItemClicked (int, const MouseEvent&) override; | |||
| Component* refreshComponentForRow (int, bool, Component*) override; | |||
| void resized() override; | |||
| void menuBarItemsChanged (MenuBarModel*) override; | |||
| void menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo&) override; | |||
| void mouseUp (const MouseEvent&) override; | |||
| void handleCommandMessage (int) override; | |||
| void addMenuBarItemsForMenu (PopupMenu&, int); | |||
| static bool hasSubMenu (const PopupMenu::Item&); | |||
| //============================================================================== | |||
| MenuBarModel* model = nullptr; | |||
| ListBox listBox {"BurgerMenuListBox", this}; | |||
| Array<Row> rows; | |||
| int lastRowClicked = -1, inputSourceIndexOfLastClick = -1, topLevelIndexClicked = -1; | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BurgerMenuComponent) | |||
| }; | |||
| } // namespace juce | |||
| @@ -44,7 +44,7 @@ class MenuWindow; | |||
| static bool canBeTriggered (const PopupMenu::Item& item) noexcept { return item.isEnabled && item.itemID != 0 && ! item.isSectionHeader; } | |||
| static bool hasActiveSubMenu (const PopupMenu::Item& item) noexcept { return item.isEnabled && item.subMenu != nullptr && item.subMenu->items.size() > 0; } | |||
| static const Colour* getColour (const PopupMenu::Item& item) noexcept { return item.colour != Colour (0x00000000) ? &item.colour : nullptr; } | |||
| static const Colour* getColour (const PopupMenu::Item& item) noexcept { return item.colour != Colour() ? &item.colour : nullptr; } | |||
| static bool hasSubMenu (const PopupMenu::Item& item) noexcept { return item.subMenu != nullptr && (item.itemID == 0 || item.subMenu->getNumItems() > 0); } | |||
| //============================================================================== | |||