/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2022 - Raw Material Software Limited 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 7 End-User License Agreement and JUCE Privacy Policy. End User License Agreement: www.juce.com/juce-7-licence Privacy Policy: www.juce.com/juce-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 { StretchableLayoutManager::StretchableLayoutManager() {} StretchableLayoutManager::~StretchableLayoutManager() {} //============================================================================== void StretchableLayoutManager::clearAllItems() { items.clear(); totalSize = 0; } void StretchableLayoutManager::setItemLayout (const int itemIndex, const double minimumSize, const double maximumSize, const double preferredSize) { auto* layout = getInfoFor (itemIndex); if (layout == nullptr) { layout = new ItemLayoutProperties(); layout->itemIndex = itemIndex; int i; for (i = 0; i < items.size(); ++i) if (items.getUnchecked (i)->itemIndex > itemIndex) break; items.insert (i, layout); } layout->minSize = minimumSize; layout->maxSize = maximumSize; layout->preferredSize = preferredSize; layout->currentSize = 0; } bool StretchableLayoutManager::getItemLayout (const int itemIndex, double& minimumSize, double& maximumSize, double& preferredSize) const { if (auto* layout = getInfoFor (itemIndex)) { minimumSize = layout->minSize; maximumSize = layout->maxSize; preferredSize = layout->preferredSize; return true; } return false; } //============================================================================== void StretchableLayoutManager::setTotalSize (const int newTotalSize) { totalSize = newTotalSize; fitComponentsIntoSpace (0, items.size(), totalSize, 0); } int StretchableLayoutManager::getItemCurrentPosition (const int itemIndex) const { int pos = 0; for (int i = 0; i < itemIndex; ++i) if (auto* layout = getInfoFor (i)) pos += layout->currentSize; return pos; } int StretchableLayoutManager::getItemCurrentAbsoluteSize (const int itemIndex) const { if (auto* layout = getInfoFor (itemIndex)) return layout->currentSize; return 0; } double StretchableLayoutManager::getItemCurrentRelativeSize (const int itemIndex) const { if (auto* layout = getInfoFor (itemIndex)) return -layout->currentSize / (double) totalSize; return 0; } void StretchableLayoutManager::setItemPosition (const int itemIndex, int newPosition) { for (int i = items.size(); --i >= 0;) { auto* layout = items.getUnchecked(i); if (layout->itemIndex == itemIndex) { auto realTotalSize = jmax (totalSize, getMinimumSizeOfItems (0, items.size())); auto minSizeAfterThisComp = getMinimumSizeOfItems (i, items.size()); auto maxSizeAfterThisComp = getMaximumSizeOfItems (i + 1, items.size()); newPosition = jmax (newPosition, totalSize - maxSizeAfterThisComp - layout->currentSize); newPosition = jmin (newPosition, realTotalSize - minSizeAfterThisComp); auto endPos = fitComponentsIntoSpace (0, i, newPosition, 0); endPos += layout->currentSize; fitComponentsIntoSpace (i + 1, items.size(), totalSize - endPos, endPos); updatePrefSizesToMatchCurrentPositions(); break; } } } //============================================================================== void StretchableLayoutManager::layOutComponents (Component** const components, int numComponents, int x, int y, int w, int h, const bool vertically, const bool resizeOtherDimension) { setTotalSize (vertically ? h : w); int pos = vertically ? y : x; for (int i = 0; i < numComponents; ++i) { if (auto* layout = getInfoFor (i)) { if (auto* c = components[i]) { if (i == numComponents - 1) { // if it's the last item, crop it to exactly fit the available space.. if (resizeOtherDimension) { if (vertically) c->setBounds (x, pos, w, jmax (layout->currentSize, h - pos)); else c->setBounds (pos, y, jmax (layout->currentSize, w - pos), h); } else { if (vertically) c->setBounds (c->getX(), pos, c->getWidth(), jmax (layout->currentSize, h - pos)); else c->setBounds (pos, c->getY(), jmax (layout->currentSize, w - pos), c->getHeight()); } } else { if (resizeOtherDimension) { if (vertically) c->setBounds (x, pos, w, layout->currentSize); else c->setBounds (pos, y, layout->currentSize, h); } else { if (vertically) c->setBounds (c->getX(), pos, c->getWidth(), layout->currentSize); else c->setBounds (pos, c->getY(), layout->currentSize, c->getHeight()); } } } pos += layout->currentSize; } } } //============================================================================== StretchableLayoutManager::ItemLayoutProperties* StretchableLayoutManager::getInfoFor (const int itemIndex) const { for (auto* i : items) if (i->itemIndex == itemIndex) return i; return nullptr; } int StretchableLayoutManager::fitComponentsIntoSpace (const int startIndex, const int endIndex, const int availableSpace, int startPos) { // calculate the total sizes double totalIdealSize = 0.0; int totalMinimums = 0; for (int i = startIndex; i < endIndex; ++i) { auto* layout = items.getUnchecked (i); layout->currentSize = sizeToRealSize (layout->minSize, totalSize); totalMinimums += layout->currentSize; totalIdealSize += sizeToRealSize (layout->preferredSize, totalSize); } if (totalIdealSize <= 0) totalIdealSize = 1.0; // now calc the best sizes.. int extraSpace = availableSpace - totalMinimums; while (extraSpace > 0) { int numWantingMoreSpace = 0; int numHavingTakenExtraSpace = 0; // first figure out how many comps want a slice of the extra space.. for (int i = startIndex; i < endIndex; ++i) { auto* layout = items.getUnchecked (i); auto sizeWanted = sizeToRealSize (layout->preferredSize, totalSize); auto bestSize = jlimit (layout->currentSize, jmax (layout->currentSize, sizeToRealSize (layout->maxSize, totalSize)), roundToInt (sizeWanted * availableSpace / totalIdealSize)); if (bestSize > layout->currentSize) ++numWantingMoreSpace; } // ..share out the extra space.. for (int i = startIndex; i < endIndex; ++i) { auto* layout = items.getUnchecked (i); auto sizeWanted = sizeToRealSize (layout->preferredSize, totalSize); auto bestSize = jlimit (layout->currentSize, jmax (layout->currentSize, sizeToRealSize (layout->maxSize, totalSize)), roundToInt (sizeWanted * availableSpace / totalIdealSize)); auto extraWanted = bestSize - layout->currentSize; if (extraWanted > 0) { auto extraAllowed = jmin (extraWanted, extraSpace / jmax (1, numWantingMoreSpace)); if (extraAllowed > 0) { ++numHavingTakenExtraSpace; --numWantingMoreSpace; layout->currentSize += extraAllowed; extraSpace -= extraAllowed; } } } if (numHavingTakenExtraSpace <= 0) break; } // ..and calculate the end position for (int i = startIndex; i < endIndex; ++i) { auto* layout = items.getUnchecked(i); startPos += layout->currentSize; } return startPos; } int StretchableLayoutManager::getMinimumSizeOfItems (const int startIndex, const int endIndex) const { int totalMinimums = 0; for (int i = startIndex; i < endIndex; ++i) totalMinimums += sizeToRealSize (items.getUnchecked (i)->minSize, totalSize); return totalMinimums; } int StretchableLayoutManager::getMaximumSizeOfItems (const int startIndex, const int endIndex) const { int totalMaximums = 0; for (int i = startIndex; i < endIndex; ++i) totalMaximums += sizeToRealSize (items.getUnchecked (i)->maxSize, totalSize); return totalMaximums; } void StretchableLayoutManager::updatePrefSizesToMatchCurrentPositions() { for (int i = 0; i < items.size(); ++i) { auto* layout = items.getUnchecked (i); layout->preferredSize = (layout->preferredSize < 0) ? getItemCurrentRelativeSize (i) : getItemCurrentAbsoluteSize (i); } } int StretchableLayoutManager::sizeToRealSize (double size, int totalSpace) { if (size < 0) size *= -totalSpace; return roundToInt (size); } } // namespace juce