diff --git a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp index 9b3555f05f..8fdbcbda13 100644 --- a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp +++ b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp @@ -900,24 +900,23 @@ void LookAndFeel_V2::drawPopupMenuUpDownArrow (Graphics& g, int width, int heigh g.fillPath (p); } -void LookAndFeel_V2::drawPopupMenuItem (Graphics& g, int width, int height, +void LookAndFeel_V2::drawPopupMenuItem (Graphics& g, const Rectangle& area, const bool isSeparator, const bool isActive, const bool isHighlighted, const bool isTicked, const bool hasSubMenu, const String& text, const String& shortcutKeyText, - Image* image, const Colour* const textColourToUse) + const Drawable* icon, const Colour* const textColourToUse) { - const float halfH = height * 0.5f; - if (isSeparator) { - const float separatorIndent = 5.5f; + Rectangle r (area.reduced (5, 0)); + r.removeFromTop (r.getHeight() / 2 - 1); g.setColour (Colour (0x33000000)); - g.drawLine (separatorIndent, halfH, width - separatorIndent, halfH); + g.fillRect (r.removeFromTop (1)); g.setColour (Colour (0x66ffffff)); - g.drawLine (separatorIndent, halfH + 1.0f, width - separatorIndent, halfH + 1.0f); + g.fillRect (r.removeFromTop (1)); } else { @@ -926,10 +925,12 @@ void LookAndFeel_V2::drawPopupMenuItem (Graphics& g, int width, int height, if (textColourToUse != nullptr) textColour = *textColourToUse; + Rectangle r (area.reduced (1)); + if (isHighlighted) { g.setColour (findColour (PopupMenu::highlightedBackgroundColourId)); - g.fillRect (1, 1, width - 2, height - 2); + g.fillRect (r); g.setColour (findColour (PopupMenu::highlightedTextColourId)); } @@ -943,33 +944,42 @@ void LookAndFeel_V2::drawPopupMenuItem (Graphics& g, int width, int height, Font font (getPopupMenuFont()); - if (font.getHeight() > height / 1.3f) - font.setHeight (height / 1.3f); + const float maxFontHeight = area.getHeight() / 1.3f; + + if (font.getHeight() > maxFontHeight) + font.setHeight (maxFontHeight); g.setFont (font); - const int leftBorder = (height * 5) / 4; - const int rightBorder = 4; + Rectangle iconArea (r.removeFromLeft ((r.getHeight() * 5) / 4).reduced (3).toFloat()); - if (image != nullptr) + if (icon != nullptr) { - g.drawImageWithin (*image, - 2, 1, leftBorder - 4, height - 2, - RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, false); + icon->drawWithin (g, iconArea, RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, 1.0f); } else if (isTicked) { const Path tick (getTickShape (1.0f)); - const float th = font.getAscent(); - const float ty = halfH - th * 0.5f; - - g.fillPath (tick, tick.getTransformToScaleToFit (2.0f, ty, (float) (leftBorder - 4), - th, true)); + g.fillPath (tick, tick.getTransformToScaleToFit (iconArea, true)); } - g.drawFittedText (text, - leftBorder, 0, width - (leftBorder + rightBorder), height, - Justification::centredLeft, 1); + if (hasSubMenu) + { + const float arrowH = 0.6f * getPopupMenuFont().getAscent(); + + const float x = (float) r.removeFromRight ((int) arrowH).getX(); + const float halfH = (float) r.getCentreY(); + + Path p; + p.addTriangle (x, halfH - arrowH * 0.5f, + x, halfH + arrowH * 0.5f, + x + arrowH * 0.6f, halfH); + + g.fillPath (p); + } + + r.removeFromRight (3); + g.drawFittedText (text, r, Justification::centredLeft, 1); if (shortcutKeyText.isNotEmpty()) { @@ -978,23 +988,7 @@ void LookAndFeel_V2::drawPopupMenuItem (Graphics& g, int width, int height, f2.setHorizontalScale (0.95f); g.setFont (f2); - g.drawText (shortcutKeyText, - leftBorder, 0, width - (leftBorder + rightBorder + 4), height, - Justification::centredRight, - true); - } - - if (hasSubMenu) - { - const float arrowH = 0.6f * getPopupMenuFont().getAscent(); - const float x = width - height * 0.6f; - - Path p; - p.addTriangle (x, halfH - arrowH * 0.5f, - x, halfH + arrowH * 0.5f, - x + arrowH * 0.6f, halfH); - - g.fillPath (p); + g.drawText (shortcutKeyText, r, Justification::centredRight, true); } } } diff --git a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h index 430c90dcbf..5ffb955022 100644 --- a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h +++ b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h @@ -133,10 +133,10 @@ public: //============================================================================== void drawPopupMenuBackground (Graphics&, int width, int height) override; - void drawPopupMenuItem (Graphics&, int width, int height, + void drawPopupMenuItem (Graphics&, const Rectangle& area, bool isSeparator, bool isActive, bool isHighlighted, bool isTicked, bool hasSubMenu, const String& text, const String& shortcutKeyText, - Image* image, const Colour* textColour) override; + const Drawable* icon, const Colour* textColour) override; Font getPopupMenuFont() override; diff --git a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.cpp b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.cpp index cffef3dc95..ee85d683e7 100644 --- a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.cpp +++ b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.cpp @@ -604,3 +604,27 @@ Button* LookAndFeel_V3::createDocumentWindowButton (int buttonType) jassertfalse; return nullptr; } + +Path LookAndFeel_V3::getTickShape (const float height) +{ + static const unsigned char pathData[] + = { 110,109,32,210,202,64,126,183,148,64,108,39,244,247,64,245,76,124,64,108,178,131,27,65,246,76,252,64,108,175,242,4,65,246,76,252, + 64,108,236,5,68,65,0,0,160,180,108,240,150,90,65,21,136,52,63,108,48,59,16,65,0,0,32,65,108,32,210,202,64,126,183,148,64, 99,101,0,0 }; + + Path p; + p.loadPathFromData (pathData, sizeof (pathData)); + p.scaleToFit (0, 0, height * 2.0f, height, true); + return p; +} + +Path LookAndFeel_V3::getCrossShape (const float height) +{ + static const unsigned char pathData[] + = { 110,109,88,57,198,65,29,90,171,65,108,63,53,154,65,8,172,126,65,108,76,55,198,65,215,163,38,65,108,141,151,175,65,82,184,242,64,108,117,147,131,65,90,100,81,65,108,184,30,47,65,82,184,242,64,108,59,223,1,65,215,163,38,65,108,84,227,89,65,8,172,126,65, + 108,35,219,1,65,29,90,171,65,108,209,34,47,65,231,251,193,65,108,117,147,131,65,207,247,149,65,108,129,149,175,65,231,251,193,65,99,101,0,0 }; + + Path p; + p.loadPathFromData (pathData, sizeof (pathData)); + p.scaleToFit (0, 0, height * 2.0f, height, true); + return p; +} diff --git a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.h b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.h index 412331e970..04bdd9ee0c 100644 --- a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.h +++ b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.h @@ -82,6 +82,9 @@ public: void drawConcertinaPanelHeader (Graphics&, const Rectangle& area, bool isMouseOver, bool isMouseDown, ConcertinaPanel&, Component&) override; + Path getTickShape (float height) override; + Path getCrossShape (float height) override; + static void createTabTextLayout (const TabBarButton& button, float length, float depth, Colour colour, TextLayout&); private: diff --git a/modules/juce_gui_basics/menus/juce_PopupMenu.cpp b/modules/juce_gui_basics/menus/juce_PopupMenu.cpp index 605bcd24ad..abfc92201c 100644 --- a/modules/juce_gui_basics/menus/juce_PopupMenu.cpp +++ b/modules/juce_gui_basics/menus/juce_PopupMenu.cpp @@ -45,7 +45,7 @@ public: const String& name, const bool active, const bool ticked, - const Image& im, + Drawable* drawable, const Colour colour, const bool useColour, CustomComponent* const custom, @@ -54,8 +54,8 @@ public: : itemID (itemId), text (name), textColour (colour), isActive (active), isSeparator (false), isTicked (ticked), - usesColour (useColour), image (im), customComp (custom), - subMenu (createCopyIfNotNull (sub)), commandManager (manager) + usesColour (useColour), iconDrawable (drawable), + customComp (custom), subMenu (createCopyIfNotNull (sub)), commandManager (manager) { if (commandManager != nullptr && itemID != 0) { @@ -92,7 +92,7 @@ public: isSeparator (other.isSeparator), isTicked (other.isTicked), usesColour (other.usesColour), - image (other.image), + iconDrawable (other.iconDrawable != nullptr ? other.iconDrawable->createCopy() : nullptr), customComp (other.customComp), subMenu (createCopyIfNotNull (other.subMenu.get())), commandManager (other.commandManager) @@ -106,7 +106,7 @@ public: String text; const Colour textColour; const bool isActive, isSeparator, isTicked, usesColour; - Image image; + ScopedPointer iconDrawable; ReferenceCountedObjectPtr customComp; ScopedPointer subMenu; ApplicationCommandManager* const commandManager; @@ -175,14 +175,14 @@ public: } getLookAndFeel() - .drawPopupMenuItem (g, getWidth(), getHeight(), + .drawPopupMenuItem (g, getLocalBounds(), itemInfo.isSeparator, itemInfo.isActive, isHighlighted, itemInfo.isTicked, itemInfo.subMenu != nullptr && (itemInfo.itemID == 0 || itemInfo.subMenu->getNumItems() > 0), mainText, endText, - itemInfo.image.isValid() ? &itemInfo.image : nullptr, + itemInfo.iconDrawable, itemInfo.usesColour ? &(itemInfo.textColour) : nullptr); } } @@ -1288,8 +1288,40 @@ void PopupMenu::clear() items.clear(); } -void PopupMenu::addItem (const int itemResultID, const String& itemText, - const bool isActive, const bool isTicked, const Image& iconToUse) +void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked) +{ + jassert (itemResultID != 0); // 0 is used as a return value to indicate that the user + // didn't pick anything, so you shouldn't use it as the id + // for an item.. + + items.add (new Item (itemResultID, itemText, isActive, isTicked, nullptr, + Colours::black, false, nullptr, nullptr, nullptr)); +} + +static Drawable* createDrawableFromImage (const Image& im) +{ + if (im.isValid()) + { + DrawableImage* d = new DrawableImage(); + d->setImage (im); + return d; + } + + return nullptr; +} + +void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked, const Image& iconToUse) +{ + jassert (itemResultID != 0); // 0 is used as a return value to indicate that the user + // didn't pick anything, so you shouldn't use it as the id + // for an item.. + + + items.add (new Item (itemResultID, itemText, isActive, isTicked, createDrawableFromImage (iconToUse), + Colours::black, false, nullptr, nullptr, nullptr)); +} + +void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked, Drawable* iconToUse) { jassert (itemResultID != 0); // 0 is used as a return value to indicate that the user // didn't pick anything, so you shouldn't use it as the id @@ -1315,7 +1347,7 @@ void PopupMenu::addCommandItem (ApplicationCommandManager* commandManager, : info.shortName, target != nullptr && (info.flags & ApplicationCommandInfo::isDisabled) == 0, (info.flags & ApplicationCommandInfo::isTicked) != 0, - Image::null, + nullptr, Colours::black, false, nullptr, nullptr, @@ -1323,50 +1355,49 @@ void PopupMenu::addCommandItem (ApplicationCommandManager* commandManager, } } -void PopupMenu::addColouredItem (const int itemResultID, - const String& itemText, - Colour itemTextColour, - const bool isActive, - const bool isTicked, - const Image& iconToUse) +void PopupMenu::addColouredItem (int itemResultID, const String& itemText, Colour itemTextColour, + bool isActive, bool isTicked, const Image& iconToUse) { jassert (itemResultID != 0); // 0 is used as a return value to indicate that the user // didn't pick anything, so you shouldn't use it as the id // for an item.. - items.add (new Item (itemResultID, itemText, isActive, isTicked, iconToUse, + items.add (new Item (itemResultID, itemText, isActive, isTicked, createDrawableFromImage (iconToUse), itemTextColour, true, nullptr, nullptr, nullptr)); } -void PopupMenu::addCustomItem (const int itemID, CustomComponent* const cc, const PopupMenu* subMenu) +void PopupMenu::addCustomItem (int itemID, CustomComponent* cc, const PopupMenu* subMenu) { jassert (itemID != 0); // 0 is used as a return value to indicate that the user // didn't pick anything, so you shouldn't use it as the id // for an item.. - items.add (new Item (itemID, String::empty, true, false, Image::null, + items.add (new Item (itemID, String::empty, true, false, nullptr, Colours::black, false, cc, subMenu, nullptr)); } -void PopupMenu::addCustomItem (const int itemResultID, - Component* customComponent, - int idealWidth, int idealHeight, - const bool triggerMenuItemAutomaticallyWhenClicked, - const PopupMenu* subMenu) +void PopupMenu::addCustomItem (int itemResultID, Component* customComponent, int idealWidth, int idealHeight, + bool triggerMenuItemAutomaticallyWhenClicked, const PopupMenu* subMenu) { - items.add (new Item (itemResultID, String::empty, true, false, Image::null, - Colours::black, false, + items.add (new Item (itemResultID, String::empty, true, false, nullptr, Colours::black, false, new HelperClasses::NormalComponentWrapper (customComponent, idealWidth, idealHeight, triggerMenuItemAutomaticallyWhenClicked), subMenu, nullptr)); } -void PopupMenu::addSubMenu (const String& subMenuName, - const PopupMenu& subMenu, - const bool isActive, - const Image& iconToUse, - const bool isTicked, - const int itemResultID) +void PopupMenu::addSubMenu (const String& subMenuName, const PopupMenu& subMenu, bool isActive) +{ + addSubMenu (subMenuName, subMenu, isActive, nullptr, false, 0); +} + +void PopupMenu::addSubMenu (const String& subMenuName, const PopupMenu& subMenu, bool isActive, + const Image& iconToUse, bool isTicked, int itemResultID) +{ + addSubMenu (subMenuName, subMenu, isActive, createDrawableFromImage (iconToUse), isTicked, itemResultID); +} + +void PopupMenu::addSubMenu (const String& subMenuName, const PopupMenu& subMenu, bool isActive, + Drawable* iconToUse, bool isTicked, int itemResultID) { items.add (new Item (itemResultID, subMenuName, isActive && (itemResultID != 0 || subMenu.getNumItems() > 0), isTicked, iconToUse, Colours::black, false, nullptr, &subMenu, nullptr)); @@ -1673,7 +1704,7 @@ void PopupMenu::CustomComponent::triggerMenuItem() { if (HelperClasses::ItemComponent* const mic = dynamic_cast (getParentComponent())) { - if (HelperClasses::MenuWindow* const pmw = dynamic_cast (mic->getParentComponent())) + if (HelperClasses::MenuWindow* const pmw = dynamic_cast (mic->getParentComponent())) { pmw->dismissMenu (&mic->itemInfo); } @@ -1727,10 +1758,10 @@ bool PopupMenu::MenuItemIterator::next() isSeparator = item->isSeparator; isTicked = item->isTicked; isEnabled = item->isActive; - isSectionHeader = dynamic_cast (static_cast (item->customComp)) != nullptr; + isSectionHeader = dynamic_cast (static_cast (item->customComp)) != nullptr; isCustomComponent = (! isSectionHeader) && item->customComp != nullptr; customColour = item->usesColour ? &(item->textColour) : nullptr; - customImage = item->image; + icon = item->iconDrawable; commandManager = item->commandManager; return true; @@ -1738,8 +1769,7 @@ bool PopupMenu::MenuItemIterator::next() void PopupMenu::MenuItemIterator::addItemTo (PopupMenu& targetMenu) { - targetMenu.items.add (new Item (itemId, itemName, isEnabled, isTicked, customImage, - customColour != nullptr ? *customColour : Colours::black, customColour != nullptr, - nullptr, - subMenu, commandManager)); + targetMenu.items.add (new Item (itemId, itemName, isEnabled, isTicked, icon != nullptr ? icon->createCopy() : nullptr, + customColour != nullptr ? *customColour : Colours::black, + customColour != nullptr, nullptr, subMenu, commandManager)); } diff --git a/modules/juce_gui_basics/menus/juce_PopupMenu.h b/modules/juce_gui_basics/menus/juce_PopupMenu.h index e4622d6a8c..82545a382e 100644 --- a/modules/juce_gui_basics/menus/juce_PopupMenu.h +++ b/modules/juce_gui_basics/menus/juce_PopupMenu.h @@ -111,18 +111,52 @@ public: @param itemText the text to show. @param isEnabled if false, the item will be shown 'greyed-out' and can't be picked @param isTicked if true, the item will be shown with a tick next to it - @param iconToUse if this is non-zero, it should be an image that will be - displayed to the left of the item. This method will take its - own copy of the image passed-in, so there's no need to keep - it hanging around. @see addSeparator, addColouredItem, addCustomItem, addSubMenu */ void addItem (int itemResultID, const String& itemText, bool isEnabled = true, - bool isTicked = false, - const Image& iconToUse = Image::null); + bool isTicked = false); + + /** Appends a new item with an icon. + + @param itemResultID the number that will be returned from the show() method + if the user picks this item. The value should never be + zero, because that's used to indicate that the user didn't + select anything. + @param itemText the text to show. + @param isEnabled if false, the item will be shown 'greyed-out' and can't be picked + @param isTicked if true, the item will be shown with a tick next to it + @param iconToUse if this is a valid image, it will be displayed to the left of the item. + + @see addSeparator, addColouredItem, addCustomItem, addSubMenu + */ + void addItem (int itemResultID, + const String& itemText, + bool isEnabled, + bool isTicked, + const Image& iconToUse); + + /** Appends a new item with an icon. + + @param itemResultID the number that will be returned from the show() method + if the user picks this item. The value should never be + zero, because that's used to indicate that the user didn't + select anything. + @param itemText the text to show. + @param isEnabled if false, the item will be shown 'greyed-out' and can't be picked + @param isTicked if true, the item will be shown with a tick next to it + @param iconToUse a Drawable object to use as the icon to the left of the item. + The menu will take ownership of this drawable object and will + delete it later when no longer needed + @see addSeparator, addColouredItem, addCustomItem, addSubMenu + */ + void addItem (int itemResultID, + const String& itemText, + bool isEnabled, + bool isTicked, + Drawable* iconToUse); /** Adds an item that represents one of the commands in a command manager object. @@ -177,8 +211,35 @@ public: */ void addSubMenu (const String& subMenuName, const PopupMenu& subMenu, - bool isEnabled = true, - const Image& iconToUse = Image::null, + bool isEnabled = true); + + /** Appends a sub-menu with an icon. + + If the menu that's passed in is empty, it will appear as an inactive item. + If the itemResultID argument is non-zero, then the sub-menu item itself can be + clicked to trigger it as a command. + */ + void addSubMenu (const String& subMenuName, + const PopupMenu& subMenu, + bool isEnabled, + const Image& iconToUse, + bool isTicked = false, + int itemResultID = 0); + + /** Appends a sub-menu with an icon. + + If the menu that's passed in is empty, it will appear as an inactive item. + If the itemResultID argument is non-zero, then the sub-menu item itself can be + clicked to trigger it as a command. + + The iconToUse parameter is a Drawable object to use as the icon to the left of + the item. The menu will take ownership of this drawable object and will delete it + later when no longer needed + */ + void addSubMenu (const String& subMenuName, + const PopupMenu& subMenu, + bool isEnabled, + Drawable* iconToUse, bool isTicked = false, int itemResultID = 0); @@ -408,7 +469,7 @@ public: bool isCustomComponent; bool isSectionHeader; const Colour* customColour; - Image customImage; + const Drawable* icon; ApplicationCommandManager* commandManager; private: @@ -493,12 +554,12 @@ public: virtual void drawPopupMenuBackground (Graphics&, int width, int height) = 0; /** Draws one of the items in a popup menu. */ - virtual void drawPopupMenuItem (Graphics&, int width, int height, + virtual void drawPopupMenuItem (Graphics&, const Rectangle& area, bool isSeparator, bool isActive, bool isHighlighted, bool isTicked, bool hasSubMenu, const String& text, const String& shortcutKeyText, - Image* icon, + const Drawable* icon, const Colour* textColour) = 0; /** Returns the size and style of font to use in popup menus. */ @@ -549,6 +610,11 @@ private: Component* createWindow (const Options&, ApplicationCommandManager**) const; int showWithOptionalCallback (const Options&, ModalComponentManager::Callback*, bool); + #if JUCE_CATCH_DEPRECATED_CODE_MISUSE + // These methods have new implementations now - see its new definition + int drawPopupMenuItem (Graphics&, int, int, bool, bool, bool, bool, bool, const String&, const String&, Image*, const Colour*) { return 0; } + #endif + JUCE_LEAK_DETECTOR (PopupMenu) };