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

PopupMenu: Allow manual column breaking

This commit is contained in:
reuk 2020-10-26 12:48:21 +00:00
parent d4450ad8ae
commit f61447fd01
2 changed files with 119 additions and 43 deletions

View file

@ -748,6 +748,28 @@ struct MenuWindow : public Component
}
void layoutMenuItems (const int maxMenuW, const int maxMenuH, int& width, int& height)
{
// Ensure we don't try to add an empty column after the final item
if (auto* last = items.getLast())
last->item.shouldBreakAfter = false;
const auto isBreak = [] (const ItemComponent* item) { return item->item.shouldBreakAfter; };
const auto numBreaks = static_cast<int> (std::count_if (items.begin(), items.end(), isBreak));
numColumns = numBreaks + 1;
if (numBreaks == 0)
insertColumnBreaks (maxMenuW, maxMenuH);
workOutManualSize (maxMenuW);
auto actualH = jmin (contentHeight, maxMenuH);
needsToScroll = contentHeight > actualH;
width = updateYPositions();
height = actualH + getLookAndFeel().getPopupMenuBorderSizeWithOptions (options) * 2;
}
void insertColumnBreaks (const int maxMenuW, const int maxMenuH)
{
numColumns = options.getMinimumNumColumns();
contentHeight = 0;
@ -766,24 +788,74 @@ struct MenuWindow : public Component
}
if (totalW > maxMenuW / 2
|| contentHeight < maxMenuH
|| numColumns >= maximumNumColumns)
|| contentHeight < maxMenuH
|| numColumns >= maximumNumColumns)
break;
++numColumns;
}
auto actualH = jmin (contentHeight, maxMenuH);
const auto itemsPerColumn = (items.size() + numColumns - 1) / numColumns;
needsToScroll = contentHeight > actualH;
for (auto i = 0;; i += itemsPerColumn)
{
const auto breakIndex = i + itemsPerColumn - 1;
width = updateYPositions();
height = actualH + getLookAndFeel().getPopupMenuBorderSizeWithOptions (options) * 2;
if (breakIndex >= items.size())
break;
items[breakIndex]->item.shouldBreakAfter = true;
}
if (! items.isEmpty())
(*std::prev (items.end()))->item.shouldBreakAfter = false;
}
int correctColumnWidths (const int maxMenuW)
{
auto totalW = std::accumulate (columnWidths.begin(), columnWidths.end(), 0);
const auto minWidth = jmin (maxMenuW, options.getMinimumWidth());
if (totalW < minWidth)
{
totalW = minWidth;
for (auto& column : columnWidths)
column = totalW / numColumns;
}
return totalW;
}
void workOutManualSize (const int maxMenuW)
{
contentHeight = 0;
columnWidths.clear();
for (auto it = items.begin(), end = items.end(); it != end;)
{
const auto isBreak = [] (const ItemComponent* item) { return item->item.shouldBreakAfter; };
const auto nextBreak = std::find_if (it, end, isBreak);
const auto columnEnd = nextBreak == end ? end : std::next (nextBreak);
const auto getMaxWidth = [] (int acc, const ItemComponent* item) { return jmax (acc, item->getWidth()); };
const auto colW = std::accumulate (it, columnEnd, options.getStandardItemHeight(), getMaxWidth);
const auto adjustedColW = jmin (maxMenuW / jmax (1, numColumns - 2),
colW + getLookAndFeel().getPopupMenuBorderSizeWithOptions (options) * 2);
const auto sumHeight = [] (int acc, const ItemComponent* item) { return acc + item->getHeight(); };
const auto colH = std::accumulate (it, columnEnd, 0, sumHeight);
contentHeight = jmax (contentHeight, colH);
columnWidths.add (adjustedColW);
it = columnEnd;
}
correctColumnWidths (maxMenuW);
}
int workOutBestSize (const int maxMenuW)
{
int totalW = 0;
contentHeight = 0;
int childNum = 0;
@ -804,24 +876,12 @@ struct MenuWindow : public Component
colW + getLookAndFeel().getPopupMenuBorderSizeWithOptions (options) * 2);
columnWidths.set (col, colW);
totalW += colW;
contentHeight = jmax (contentHeight, colH);
childNum += numChildren;
}
// width must never be larger than the screen
auto minWidth = jmin (maxMenuW, options.getMinimumWidth());
if (totalW < minWidth)
{
totalW = minWidth;
for (int col = 0; col < numColumns; ++col)
columnWidths.set (0, totalW / numColumns);
}
return totalW;
return correctColumnWidths (maxMenuW);
}
void ensureItemIsVisible (const int itemID, int wantedY)
@ -924,34 +984,31 @@ struct MenuWindow : public Component
int updateYPositions()
{
int x = 0;
int childNum = 0;
const auto separatorWidth = getLookAndFeel().getPopupMenuColumnSeparatorWidthWithOptions (options);
const auto initialY = getLookAndFeel().getPopupMenuBorderSizeWithOptions (options)
- (childYOffset + (getY() - windowPos.getY()));
for (int col = 0; col < numColumns; ++col)
auto col = 0;
auto x = 0;
auto y = initialY;
for (const auto& item : items)
{
auto numChildren = jmin (items.size() - childNum,
(items.size() + numColumns - 1) / numColumns);
jassert (col < columnWidths.size());
const auto columnWidth = columnWidths[col];
item->setBounds (x, y, columnWidth, item->getHeight());
y += item->getHeight();
auto colW = columnWidths[col];
auto y = getLookAndFeel().getPopupMenuBorderSizeWithOptions (options)
- (childYOffset + (getY() - windowPos.getY()));
for (int i = 0; i < numChildren; ++i)
if (item->item.shouldBreakAfter)
{
auto* c = items.getUnchecked (childNum + i);
c->setBounds (x, y, colW, c->getHeight());
y += c->getHeight();
col += 1;
x += columnWidth + separatorWidth;
y = initialY;
}
x += colW + separatorWidth;
childNum += numChildren;
}
x -= separatorWidth;
return x;
return std::accumulate (columnWidths.begin(), columnWidths.end(), 0)
+ (separatorWidth * (columnWidths.size() - 1));
}
void setCurrentlyHighlightedChild (ItemComponent* child)
@ -1405,9 +1462,9 @@ PopupMenu::Item::Item (const Item& other)
isEnabled (other.isEnabled),
isTicked (other.isTicked),
isSeparator (other.isSeparator),
isSectionHeader (other.isSectionHeader)
{
}
isSectionHeader (other.isSectionHeader),
shouldBreakAfter (other.shouldBreakAfter)
{}
PopupMenu::Item& PopupMenu::Item::operator= (const Item& other)
{
@ -1425,6 +1482,7 @@ PopupMenu::Item& PopupMenu::Item::operator= (const Item& other)
isTicked = other.isTicked;
isSeparator = other.isSeparator;
isSectionHeader = other.isSectionHeader;
shouldBreakAfter = other.shouldBreakAfter;
return *this;
}
@ -1685,6 +1743,12 @@ void PopupMenu::addSectionHeader (String title)
addItem (std::move (i));
}
void PopupMenu::addColumnBreak()
{
if (! items.isEmpty())
std::prev (items.end())->shouldBreakAfter = true;
}
//==============================================================================
PopupMenu::Options::Options()
{

View file

@ -179,6 +179,9 @@ public:
/** True if this menu item is a section header. */
bool isSectionHeader = false;
/** True if this is the final item in the current column. */
bool shouldBreakAfter = false;
/** Sets the isTicked flag (and returns a reference to this item to allow chaining). */
Item& setTicked (bool shouldBeTicked = true) & noexcept;
/** Sets the isEnabled flag (and returns a reference to this item to allow chaining). */
@ -410,6 +413,15 @@ public:
*/
void addSectionHeader (String title);
/** Adds a column break to the menu, to help break it up into sections.
Subsequent items will be placed in a new column, rather than being appended
to the current column.
If a menu contains explicit column breaks, the menu will never add additional
breaks.
*/
void addColumnBreak();
/** Returns the number of items that the menu currently contains.
(This doesn't count separators).
*/