1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-24 01:54:22 +00:00

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`.
This commit is contained in:
reuk 2020-06-08 17:45:45 +01:00
parent 37c2dbe547
commit f04e11c4bb
5 changed files with 251 additions and 49 deletions

View file

@ -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

View file

@ -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<int>& 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<int>& area,
}
}
void LookAndFeel_V2::drawPopupMenuSectionHeader (Graphics& g, const Rectangle<int>& area, const String& sectionName)
void LookAndFeel_V2::drawPopupMenuItemWithOptions (Graphics& g, const Rectangle<int>& 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<int>& 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<in
Justification::bottomLeft, 1);
}
void LookAndFeel_V2::drawPopupMenuSectionHeaderWithOptions (Graphics& g, const Rectangle<int>& 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)
{

View file

@ -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<int>& 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<int>& area,
bool isHighlighted,
const PopupMenu::Item& item,
const PopupMenu::Options&) override;
void drawPopupMenuSectionHeader (Graphics&, const Rectangle<int>& area,
const String& sectionName) override;
void drawPopupMenuSectionHeaderWithOptions (Graphics&, const Rectangle<int>& 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,

View file

@ -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<CustomComponent> 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<int> (getLookAndFeel().getPopupMenuBorderSize()));
BorderSize<int> (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<int>&,
bool, bool, bool,
bool, bool,
const String&,
const String&,
const Drawable*,
const Colour*) {}
void PopupMenu::LookAndFeelMethods::drawPopupMenuSectionHeader (Graphics&, const Rectangle<int>&,
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

View file

@ -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<int>& 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<int>& area,
const String& sectionName) = 0;
/** Draws one of the items in a popup menu. */
virtual void drawPopupMenuItemWithOptions (Graphics&, const Rectangle<int>& area,
bool isHighlighted,
const Item& item,
const Options&) = 0;
virtual void drawPopupMenuSectionHeader (Graphics&, const Rectangle<int>&,
const String&);
virtual void drawPopupMenuSectionHeaderWithOptions (Graphics&, const Rectangle<int>& 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: