Files
dota_factory/src/lib/core/FloatingPanelPlacement.cpp

111 lines
4.4 KiB
C++

#include "FloatingPanelPlacement.h"
#include <algorithm>
namespace
{
// The part every placement shares, whatever put the panel where it wants to be: it is
// pushed inside band rather than hanging off it, shortened to what its own column has
// free, and lifted by however much of it hangs below that. wantedLeftPx and wantedTopPx
// are where the panel would stand if nothing were in the way.
QRect fitInBand(const QRect& band, int wantedLeftPx, int wantedTopPx, QSize wantedSize,
const std::vector<QRect>& occupiedRects, int marginPx)
{
const int widthPx = std::min(wantedSize.width(), band.width());
int leftPx = std::min(wantedLeftPx, band.right() - widthPx + 1);
leftPx = std::max(leftPx, band.left());
// Only the widgets its own column meets can shorten it.
const int bottomPx = getAvailableBottomPx(band, occupiedRects, leftPx,
leftPx + widthPx - 1, marginPx);
const int heightPx =
std::min(wantedSize.height(), std::max(0, bottomPx - band.top() + 1));
// Never above the band: a panel taller than the space left is capped instead, and
// scrolls.
int topPx = std::max(wantedTopPx, band.top());
topPx = std::min(topPx, bottomPx - heightPx + 1);
topPx = std::max(topPx, band.top());
return QRect(leftPx, topPx, widthPx, heightPx);
}
} // namespace
int getAvailableBottomPx(const QRect& band, const std::vector<QRect>& occupiedRects,
int leftPx, int rightPx, int marginPx)
{
int bottomPx = band.bottom();
for (const QRect& occupied : occupiedRects)
{
if (occupied.isEmpty())
{
continue;
}
// Only what is actually in the way counts: a widget entirely to one side of this
// span is not below it, however tall it is.
if (occupied.right() < leftPx || occupied.left() > rightPx)
{
continue;
}
bottomPx = std::min(bottomPx, occupied.top() - marginPx - 1);
}
return bottomPx;
}
PanelSide chooseSide(const QRect& band, const QRect& anchorRect, int widthPx,
int marginPx)
{
// What each side offers: the gap between the anchor and that edge of the band, less
// the margin the panel keeps from the anchor.
const int roomRightPx = band.right() - anchorRect.right() - marginPx;
const int roomLeftPx = anchorRect.left() - band.left() - marginPx;
if (roomRightPx >= widthPx)
{
return PanelSide::Right;
}
if (roomLeftPx >= widthPx)
{
return PanelSide::Left;
}
// Neither side can hold it without covering the selection, so it goes where it
// covers the least of it.
return (roomRightPx >= roomLeftPx) ? PanelSide::Right : PanelSide::Left;
}
QRect placeBesideAnchor(const QRect& band, const QRect& anchorRect, PanelSide side,
QSize wantedSize, const std::vector<QRect>& occupiedRects,
int marginPx)
{
const int widthPx = std::min(wantedSize.width(), band.width());
// Against the anchor on the chosen side, growing away from it: the edge facing the
// selection is the one that stays put as the panel's content resizes. A panel that
// does not fit there is pushed back inside the view rather than hanging off it, which
// is what puts it over the selection when neither side had room.
const int wantedLeftPx = (side == PanelSide::Right)
? anchorRect.right() + marginPx + 1
: anchorRect.left() - marginPx - widthPx;
// Top-aligned with the anchor, then lifted by however much of it hangs below what is
// free.
return fitInBand(band, wantedLeftPx, anchorRect.top(), wantedSize, occupiedRects,
marginPx);
}
QRect placeAtDesiredTopLeft(const QRect& band, const QPoint& desiredTopLeftPx,
QSize wantedSize, const std::vector<QRect>& occupiedRects,
int marginPx)
{
// Where the player dropped it, resolved by the same rules as any other placement
// (REQ-UI-SELECTION-PANEL-DRAG). Nothing here is written back to the desired point:
// a panel lifted above the build button bar returns to where it was dropped as soon
// as the bar stops meeting its column.
return fitInBand(band, desiredTopLeftPx.x(), desiredTopLeftPx.y(), wantedSize,
occupiedRects, marginPx);
}