diff --git a/examples/Demo/Source/Demos/WidgetsDemo.cpp b/examples/Demo/Source/Demos/WidgetsDemo.cpp index 4edb1da085..26d8a47ee8 100644 --- a/examples/Demo/Source/Demos/WidgetsDemo.cpp +++ b/examples/Demo/Source/Demos/WidgetsDemo.cpp @@ -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 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()) { @@ -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 (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 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(); } diff --git a/examples/Demo/Source/Main.cpp b/examples/Demo/Source/Main.cpp index 5ad91100c0..a28c93bf66 100644 --- a/examples/Demo/Source/Main.cpp +++ b/examples/Demo/Source/Main.cpp @@ -54,6 +54,11 @@ public: mainWindow.reset(); } + void backButtonPressed() override + { + MainAppWindow::getSharedSidePanel().showOrHide (false); + } + //============================================================================== void systemRequestedQuit() override { diff --git a/examples/Demo/Source/MainWindow.cpp b/examples/Demo/Source/MainWindow.cpp index 8cf20c6897..9e33c46d7c 100644 --- a/examples/Demo/Source/MainWindow.cpp +++ b/examples/Demo/Source/MainWindow.cpp @@ -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 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 ( diff --git a/examples/Demo/Source/MainWindow.h b/examples/Demo/Source/MainWindow.h index 3fde246662..825c20828f 100644 --- a/examples/Demo/Source/MainWindow.h +++ b/examples/Demo/Source/MainWindow.h @@ -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); diff --git a/modules/juce_gui_basics/juce_gui_basics.cpp b/modules/juce_gui_basics/juce_gui_basics.cpp index ad1e87bfdd..51e3fe1fb8 100644 --- a/modules/juce_gui_basics/juce_gui_basics.cpp +++ b/modules/juce_gui_basics/juce_gui_basics.cpp @@ -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" diff --git a/modules/juce_gui_basics/juce_gui_basics.h b/modules/juce_gui_basics/juce_gui_basics.h index 7a4070ac7d..79d478a1cc 100644 --- a/modules/juce_gui_basics/juce_gui_basics.h +++ b/modules/juce_gui_basics/juce_gui_basics.h @@ -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" diff --git a/modules/juce_gui_basics/layout/juce_SidePanel.h b/modules/juce_gui_basics/layout/juce_SidePanel.h index a15a926a1e..6f894c6d0c 100644 --- a/modules/juce_gui_basics/layout/juce_SidePanel.h +++ b/modules/juce_gui_basics/layout/juce_SidePanel.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 contentComponent; Label titleLabel; - ShapeButton dismissButton {"dismissButton", Colours::lightgrey, Colours::lightgrey, Colours::white}; + ShapeButton dismissButton { "dismissButton", Colours::lightgrey, Colours::lightgrey, Colours::white }; Rectangle shadowArea; diff --git a/modules/juce_gui_basics/menus/juce_BurgerMenuComponent.cpp b/modules/juce_gui_basics/menus/juce_BurgerMenuComponent.cpp new file mode 100644 index 0000000000..070b45d0d0 --- /dev/null +++ b/modules/juce_gui_basics/menus/juce_BurgerMenuComponent.cpp @@ -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& customComponent) + { + setInterceptsMouseClicks (false, true); + update (customComponent); + } + + void update (const ReferenceCountedObjectPtr& newComponent) + { + jassert (newComponent != nullptr); + + if (newComponent != custom) + { + if (custom != nullptr) + removeChildComponent (custom); + + custom = newComponent; + addAndMakeVisible (custom); + resized(); + } + } + + void resized() override + { + custom->setBounds (getLocalBounds()); + } + + ReferenceCountedObjectPtr 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 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 (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 diff --git a/modules/juce_gui_basics/menus/juce_BurgerMenuComponent.h b/modules/juce_gui_basics/menus/juce_BurgerMenuComponent.h new file mode 100644 index 0000000000..63c65259b3 --- /dev/null +++ b/modules/juce_gui_basics/menus/juce_BurgerMenuComponent.h @@ -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 rows; + + int lastRowClicked = -1, inputSourceIndexOfLastClick = -1, topLevelIndexClicked = -1; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BurgerMenuComponent) +}; + +} // namespace juce diff --git a/modules/juce_gui_basics/menus/juce_PopupMenu.cpp b/modules/juce_gui_basics/menus/juce_PopupMenu.cpp index c97c3b4ea3..60e5439a24 100644 --- a/modules/juce_gui_basics/menus/juce_PopupMenu.cpp +++ b/modules/juce_gui_basics/menus/juce_PopupMenu.cpp @@ -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); } //==============================================================================