From f04e11c4bbc7cc01470373761df65b36ea289d28 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 8 Jun 2020 17:45:45 +0100 Subject: [PATCH] PopupMenu: Allow theming of popup menu background based on Options Adds a new `drawPopupMenuBackgroundForOption` member function to `PopupMenu::LookAndFeelMethods`. By default this will pass through to `drawPopupMenuBackground`, but it can be overridden in cases where the background appearance depends on the current `Options`. --- BREAKING-CHANGES.txt | 26 ++++ .../lookandfeel/juce_LookAndFeel_V2.cpp | 68 ++++++++- .../lookandfeel/juce_LookAndFeel_V2.h | 28 ++++ .../juce_gui_basics/menus/juce_PopupMenu.cpp | 129 ++++++++++++------ .../juce_gui_basics/menus/juce_PopupMenu.h | 49 +++++-- 5 files changed, 251 insertions(+), 49 deletions(-) diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index ec6061dbb8..72d68c4025 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -4,6 +4,32 @@ JUCE breaking changes Develop ======= +Change +------ +New pure virtual methods accepting `PopupMenu::Options` arguments have been +added to `PopupMenu::LookAndFeelMethods`. + +Possible Issues +--------------- +Classes derived from `PopupMenu::LookAndFeelMethods`, such as custom +LookAndFeel classes, will not compile unless these pure virtual methods are +implemented. + +Workaround +---------- +The old LookAndFeel methods still exist, so if the new Options parameter is not +useful in your application, your implementation of +`PopupMenu::LookAndFeelMethods` can simply forward to the old methods. For +example, your implementation of `drawPopupMenuBackgroundWithOptions` can +internally call your existing `drawPopupMenuBackground` implementation. + +Rationale +--------- +Allowing the LookAndFeelMethods to access the popup menu's options allows for +more flexible styling. For example, a theme may wish to query the menu's target +component or parent for colours to use. + + Change ------ A typo in the JUCEUtils CMake script that caused the wrong manufacturer code to diff --git a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp index 4b53000bb3..d9bbb27f91 100644 --- a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp +++ b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp @@ -890,6 +890,20 @@ void LookAndFeel_V2::getIdealPopupMenuItemSize (const String& text, const bool i } } +void LookAndFeel_V2::getIdealPopupMenuItemSizeWithOptions (const String& text, + bool isSeparator, + int standardMenuItemHeight, + int& idealWidth, + int& idealHeight, + const PopupMenu::Options&) +{ + getIdealPopupMenuItemSize (text, + isSeparator, + standardMenuItemHeight, + idealWidth, + idealHeight); +} + void LookAndFeel_V2::drawPopupMenuBackground (Graphics& g, int width, int height) { auto background = findColour (PopupMenu::backgroundColourId); @@ -906,6 +920,14 @@ void LookAndFeel_V2::drawPopupMenuBackground (Graphics& g, int width, int height #endif } +void LookAndFeel_V2::drawPopupMenuBackgroundWithOptions (Graphics& g, + int width, + int height, + const PopupMenu::Options&) +{ + drawPopupMenuBackground (g, width, height); +} + void LookAndFeel_V2::drawPopupMenuUpDownArrow (Graphics& g, int width, int height, bool isScrollUpArrow) { auto background = findColour (PopupMenu::backgroundColourId); @@ -931,6 +953,14 @@ void LookAndFeel_V2::drawPopupMenuUpDownArrow (Graphics& g, int width, int heigh g.fillPath (p); } +void LookAndFeel_V2::drawPopupMenuUpDownArrowWithOptions (Graphics& g, + int width, int height, + bool isScrollUpArrow, + const PopupMenu::Options&) +{ + drawPopupMenuUpDownArrow (g, width, height, isScrollUpArrow); +} + void LookAndFeel_V2::drawPopupMenuItem (Graphics& g, const Rectangle& area, const bool isSeparator, const bool isActive, const bool isHighlighted, const bool isTicked, @@ -1024,7 +1054,31 @@ void LookAndFeel_V2::drawPopupMenuItem (Graphics& g, const Rectangle& area, } } -void LookAndFeel_V2::drawPopupMenuSectionHeader (Graphics& g, const Rectangle& area, const String& sectionName) +void LookAndFeel_V2::drawPopupMenuItemWithOptions (Graphics& g, const Rectangle& area, + bool isHighlighted, + const PopupMenu::Item& item, + const PopupMenu::Options&) +{ + const auto colour = item.colour != Colour() ? &item.colour : nullptr; + const auto hasSubMenu = item.subMenu != nullptr + && (item.itemID == 0 || item.subMenu->getNumItems() > 0); + + drawPopupMenuItem (g, + area, + item.isSeparator, + item.isEnabled, + isHighlighted, + item.isTicked, + hasSubMenu, + item.text, + item.shortcutKeyDescription, + item.image.get(), + colour); +} + +void LookAndFeel_V2::drawPopupMenuSectionHeader (Graphics& g, + const Rectangle& area, + const String& sectionName) { g.setFont (getPopupMenuFont().boldened()); g.setColour (findColour (PopupMenu::headerTextColourId)); @@ -1034,6 +1088,13 @@ void LookAndFeel_V2::drawPopupMenuSectionHeader (Graphics& g, const Rectangle& area, + const String& sectionName, + const PopupMenu::Options&) +{ + drawPopupMenuSectionHeader (g, area, sectionName); +} + //============================================================================== int LookAndFeel_V2::getMenuWindowFlags() { @@ -1098,6 +1159,11 @@ bool LookAndFeel_V2::shouldPopupMenuScaleWithTargetComponent (const PopupMenu::O int LookAndFeel_V2::getPopupMenuBorderSize() { return 2; } +int LookAndFeel_V2::getPopupMenuBorderSizeWithOptions (const PopupMenu::Options&) +{ + return getPopupMenuBorderSize(); +} + //============================================================================== void LookAndFeel_V2::fillTextEditorBackground (Graphics& g, int /*width*/, int /*height*/, TextEditor& textEditor) { diff --git a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h index a6697d62f5..7d11c4c545 100644 --- a/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h +++ b/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h @@ -155,21 +155,47 @@ public: //============================================================================== void drawPopupMenuBackground (Graphics&, int width, int height) override; + void drawPopupMenuBackgroundWithOptions (Graphics&, + int width, + int height, + const PopupMenu::Options&) override; void drawPopupMenuItem (Graphics&, const Rectangle& area, bool isSeparator, bool isActive, bool isHighlighted, bool isTicked, bool hasSubMenu, const String& text, const String& shortcutKeyText, const Drawable* icon, const Colour* textColour) override; + void drawPopupMenuItemWithOptions (Graphics&, const Rectangle& area, + bool isHighlighted, + const PopupMenu::Item& item, + const PopupMenu::Options&) override; + void drawPopupMenuSectionHeader (Graphics&, const Rectangle& area, const String& sectionName) override; + void drawPopupMenuSectionHeaderWithOptions (Graphics&, const Rectangle& area, + const String& sectionName, + const PopupMenu::Options&) override; + Font getPopupMenuFont() override; void drawPopupMenuUpDownArrow (Graphics&, int width, int height, bool isScrollUpArrow) override; + void drawPopupMenuUpDownArrowWithOptions (Graphics&, + int width, int height, + bool isScrollUpArrow, + const PopupMenu::Options&) override; + void getIdealPopupMenuItemSize (const String& text, bool isSeparator, int standardMenuItemHeight, int& idealWidth, int& idealHeight) override; + + void getIdealPopupMenuItemSizeWithOptions (const String& text, + bool isSeparator, + int standardMenuItemHeight, + int& idealWidth, + int& idealHeight, + const PopupMenu::Options&) override; + int getMenuWindowFlags() override; void preparePopupMenuWindow (Component&) override; @@ -189,6 +215,8 @@ public: int getPopupMenuBorderSize() override; + int getPopupMenuBorderSizeWithOptions (const PopupMenu::Options&) override; + //============================================================================== void drawComboBox (Graphics&, int width, int height, bool isMouseButtonDown, int buttonX, int buttonY, int buttonW, int buttonH, diff --git a/modules/juce_gui_basics/menus/juce_PopupMenu.cpp b/modules/juce_gui_basics/menus/juce_PopupMenu.cpp index a71c6417dd..145f40fc23 100644 --- a/modules/juce_gui_basics/menus/juce_PopupMenu.cpp +++ b/modules/juce_gui_basics/menus/juce_PopupMenu.cpp @@ -43,40 +43,51 @@ struct 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() ? &item.colour : nullptr; } -static bool hasSubMenu (const PopupMenu::Item& item) noexcept { return item.subMenu != nullptr && (item.itemID == 0 || item.subMenu->getNumItems() > 0); } //============================================================================== struct HeaderItemComponent : public PopupMenu::CustomComponent { - HeaderItemComponent (const String& name) : PopupMenu::CustomComponent (false) + HeaderItemComponent (const String& name, const Options& opts) + : CustomComponent (false), options (opts) { setName (name); } void paint (Graphics& g) override { - getLookAndFeel().drawPopupMenuSectionHeader (g, getLocalBounds(), getName()); + getLookAndFeel().drawPopupMenuSectionHeaderWithOptions (g, + getLocalBounds(), + getName(), + options); } void getIdealSize (int& idealWidth, int& idealHeight) override { - getLookAndFeel().getIdealPopupMenuItemSize (getName(), false, -1, idealWidth, idealHeight); + getLookAndFeel().getIdealPopupMenuItemSizeWithOptions (getName(), + false, + -1, + idealWidth, + idealHeight, + options); idealHeight += idealHeight / 2; idealWidth += idealWidth / 4; } + const Options& options; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HeaderItemComponent) }; //============================================================================== struct ItemComponent : public Component { - ItemComponent (const PopupMenu::Item& i, int standardItemHeight, MenuWindow& parent) - : item (i), customComp (i.customComponent) + ItemComponent (const PopupMenu::Item& i, + const PopupMenu::Options& o, + MenuWindow& parent) + : item (i), options (o), customComp (i.customComponent) { if (item.isSectionHeader) - customComp = *new HeaderItemComponent (item.text); + customComp = *new HeaderItemComponent (item.text, options); if (customComp != nullptr) { @@ -90,7 +101,7 @@ struct ItemComponent : public Component int itemW = 80; int itemH = 16; - getIdealSize (itemW, itemH, standardItemHeight); + getIdealSize (itemW, itemH, options.getStandardItemHeight()); setSize (itemW, jlimit (1, 600, itemH)); addMouseListener (&parent, false); @@ -109,31 +120,29 @@ struct ItemComponent : public Component if (customComp != nullptr) customComp->getIdealSize (idealWidth, idealHeight); else - getLookAndFeel().getIdealPopupMenuItemSize (getTextForMeasurement(), - item.isSeparator, - standardItemHeight, - idealWidth, idealHeight); + getLookAndFeel().getIdealPopupMenuItemSizeWithOptions (getTextForMeasurement(), + item.isSeparator, + standardItemHeight, + idealWidth, idealHeight, + options); } void paint (Graphics& g) override { if (customComp == nullptr) - getLookAndFeel().drawPopupMenuItem (g, getLocalBounds(), - item.isSeparator, - item.isEnabled, - isHighlighted, - item.isTicked, - hasSubMenu (item), - item.text, - item.shortcutKeyDescription, - item.image.get(), - getColour (item)); + getLookAndFeel().drawPopupMenuItemWithOptions (g, getLocalBounds(), + isHighlighted, + item, + options); } void resized() override { if (auto* child = getChildComponent (0)) - child->setBounds (getLocalBounds().reduced (getLookAndFeel().getPopupMenuBorderSize(), 0)); + { + const auto border = getLookAndFeel().getPopupMenuBorderSizeWithOptions (options); + child->setBounds (getLocalBounds().reduced (border, 0)); + } } void setHighlighted (bool shouldBeHighlighted) @@ -154,6 +163,7 @@ struct ItemComponent : public Component PopupMenu::Item item; private: + const PopupMenu::Options& options; // NB: we use a copy of the one from the item info in case we're using our own section comp ReferenceCountedObjectPtr customComp; bool isHighlighted = false; @@ -233,7 +243,7 @@ struct MenuWindow : public Component auto& item = menu.items.getReference (i); if (i + 1 < menu.items.size() || ! item.isSeparator) - items.add (new ItemComponent (item, options.getStandardItemHeight(), *this)); + items.add (new ItemComponent (item, options, *this)); } auto targetArea = options.getTargetScreenArea() / scaleFactor; @@ -286,7 +296,7 @@ struct MenuWindow : public Component if (isOpaque()) g.fillAll (Colours::white); - getLookAndFeel().drawPopupMenuBackground (g, getWidth(), getHeight()); + getLookAndFeel().drawPopupMenuBackgroundWithOptions (g, getWidth(), getHeight(), options); } void paintOverChildren (Graphics& g) override @@ -295,17 +305,27 @@ struct MenuWindow : public Component if (parentComponent != nullptr) lf.drawResizableFrame (g, getWidth(), getHeight(), - BorderSize (getLookAndFeel().getPopupMenuBorderSize())); + BorderSize (getLookAndFeel().getPopupMenuBorderSizeWithOptions (options))); if (canScroll()) { if (isTopScrollZoneActive()) - lf.drawPopupMenuUpDownArrow (g, getWidth(), PopupMenuSettings::scrollZone, true); + { + lf.drawPopupMenuUpDownArrowWithOptions (g, + getWidth(), + PopupMenuSettings::scrollZone, + true, + options); + } if (isBottomScrollZoneActive()) { g.setOrigin (0, getHeight() - PopupMenuSettings::scrollZone); - lf.drawPopupMenuUpDownArrow (g, getWidth(), PopupMenuSettings::scrollZone, false); + lf.drawPopupMenuUpDownArrowWithOptions (g, + getWidth(), + PopupMenuSettings::scrollZone, + false, + options); } } } @@ -621,7 +641,7 @@ struct MenuWindow : public Component return parentComponent->getLocalArea (nullptr, parentComponent->getScreenBounds() - .reduced (getLookAndFeel().getPopupMenuBorderSize()) + .reduced (getLookAndFeel().getPopupMenuBorderSizeWithOptions (options)) .getIntersection (parentArea)); } @@ -690,7 +710,7 @@ struct MenuWindow : public Component x = tendTowardsRight ? jmin (parentArea.getRight() - widthToUse - 4, target.getRight()) : jmax (parentArea.getX() + 4, target.getX() - widthToUse); - if (getLookAndFeel().getPopupMenuBorderSize() == 0) // workaround for dismissing the window on mouse up when border size is 0 + if (getLookAndFeel().getPopupMenuBorderSizeWithOptions (options) == 0) // workaround for dismissing the window on mouse up when border size is 0 x += tendTowardsRight ? 1 : -1; y = target.getCentreY() > parentArea.getCentreY() ? jmax (parentArea.getY(), target.getBottom() - heightToUse) @@ -738,7 +758,7 @@ struct MenuWindow : public Component needsToScroll = contentHeight > actualH; width = updateYPositions(); - height = actualH + getLookAndFeel().getPopupMenuBorderSize() * 2; + height = actualH + getLookAndFeel().getPopupMenuBorderSizeWithOptions (options) * 2; } int workOutBestSize (const int maxMenuW) @@ -760,7 +780,8 @@ struct MenuWindow : public Component colH += items.getUnchecked (childNum + i)->getHeight(); } - colW = jmin (maxMenuW / jmax (1, numColumns - 2), colW + getLookAndFeel().getPopupMenuBorderSize() * 2); + colW = jmin (maxMenuW / jmax (1, numColumns - 2), + colW + getLookAndFeel().getPopupMenuBorderSizeWithOptions (options) * 2); columnWidths.set (col, colW); totalW += colW; @@ -854,11 +875,21 @@ struct MenuWindow : public Component { childYOffset += delta; - if (delta < 0) - childYOffset = jmax (childYOffset, 0); - else if (delta > 0) - childYOffset = jmin (childYOffset, - contentHeight - windowPos.getHeight() + getLookAndFeel().getPopupMenuBorderSize()); + childYOffset = [&] + { + if (delta < 0) + return jmax (childYOffset, 0); + + if (delta > 0) + { + const auto limit = contentHeight + - windowPos.getHeight() + + getLookAndFeel().getPopupMenuBorderSizeWithOptions (options); + return jmin (childYOffset, limit); + } + + return childYOffset; + }(); updateYPositions(); } @@ -882,7 +913,8 @@ struct MenuWindow : public Component (items.size() + numColumns - 1) / numColumns); auto colW = columnWidths[col]; - auto y = getLookAndFeel().getPopupMenuBorderSize() - (childYOffset + (getY() - windowPos.getY())); + auto y = getLookAndFeel().getPopupMenuBorderSizeWithOptions (options) + - (childYOffset + (getY() - windowPos.getY())); for (int i = 0; i < numChildren; ++i) { @@ -2020,4 +2052,23 @@ PopupMenu::Item& PopupMenu::MenuItemIterator::getItem() const return *(currentItem); } +void PopupMenu::LookAndFeelMethods::drawPopupMenuBackground (Graphics&, int, int) {} + +void PopupMenu::LookAndFeelMethods::drawPopupMenuItem (Graphics&, const Rectangle&, + bool, bool, bool, + bool, bool, + const String&, + const String&, + const Drawable*, + const Colour*) {} + +void PopupMenu::LookAndFeelMethods::drawPopupMenuSectionHeader (Graphics&, const Rectangle&, + const String&) {} + +void PopupMenu::LookAndFeelMethods::drawPopupMenuUpDownArrow (Graphics&, int, int, bool) {} + +void PopupMenu::LookAndFeelMethods::getIdealPopupMenuItemSize (const String&, bool, int, int&, int&) {} + +int PopupMenu::LookAndFeelMethods::getPopupMenuBorderSize() { return 0; } + } // namespace juce diff --git a/modules/juce_gui_basics/menus/juce_PopupMenu.h b/modules/juce_gui_basics/menus/juce_PopupMenu.h index ed12b5ee73..c92cd2c26d 100644 --- a/modules/juce_gui_basics/menus/juce_PopupMenu.h +++ b/modules/juce_gui_basics/menus/juce_PopupMenu.h @@ -753,7 +753,13 @@ public: virtual ~LookAndFeelMethods() = default; /** Fills the background of a popup menu component. */ - virtual void drawPopupMenuBackground (Graphics&, int width, int height) = 0; + virtual void drawPopupMenuBackground (Graphics&, int width, int height); + + /** Fills the background of a popup menu component. */ + virtual void drawPopupMenuBackgroundWithOptions (Graphics&, + int width, + int height, + const Options&) = 0; /** Draws one of the items in a popup menu. */ virtual void drawPopupMenuItem (Graphics&, const Rectangle& area, @@ -762,24 +768,47 @@ public: const String& text, const String& shortcutKeyText, const Drawable* icon, - const Colour* textColour) = 0; + const Colour* textColour); - virtual void drawPopupMenuSectionHeader (Graphics&, const Rectangle& area, - const String& sectionName) = 0; + /** Draws one of the items in a popup menu. */ + virtual void drawPopupMenuItemWithOptions (Graphics&, const Rectangle& area, + bool isHighlighted, + const Item& item, + const Options&) = 0; + + virtual void drawPopupMenuSectionHeader (Graphics&, const Rectangle&, + const String&); + + virtual void drawPopupMenuSectionHeaderWithOptions (Graphics&, const Rectangle& area, + const String& sectionName, + const Options&) = 0; /** Returns the size and style of font to use in popup menus. */ virtual Font getPopupMenuFont() = 0; virtual void drawPopupMenuUpDownArrow (Graphics&, int width, int height, - bool isScrollUpArrow) = 0; + bool isScrollUpArrow); + + virtual void drawPopupMenuUpDownArrowWithOptions (Graphics&, + int width, int height, + bool isScrollUpArrow, + const Options&) = 0; /** Finds the best size for an item in a popup menu. */ virtual void getIdealPopupMenuItemSize (const String& text, bool isSeparator, int standardMenuItemHeight, int& idealWidth, - int& idealHeight) = 0; + int& idealHeight); + + /** Finds the best size for an item in a popup menu. */ + virtual void getIdealPopupMenuItemSizeWithOptions (const String& text, + bool isSeparator, + int standardMenuItemHeight, + int& idealWidth, + int& idealHeight, + const Options&) = 0; virtual int getMenuWindowFlags() = 0; @@ -801,15 +830,17 @@ public: bool isMouseOverBar, MenuBarComponent&) = 0; - virtual Component* getParentComponentForMenuOptions (const PopupMenu::Options& options) = 0; + virtual Component* getParentComponentForMenuOptions (const Options& options) = 0; virtual void preparePopupMenuWindow (Component& newWindow) = 0; /** Return true if you want your popup menus to scale with the target component's AffineTransform or scale factor */ - virtual bool shouldPopupMenuScaleWithTargetComponent (const PopupMenu::Options& options) = 0; + virtual bool shouldPopupMenuScaleWithTargetComponent (const Options& options) = 0; - virtual int getPopupMenuBorderSize() = 0; + virtual int getPopupMenuBorderSize(); + + virtual int getPopupMenuBorderSizeWithOptions (const Options&) = 0; }; private: