function FlowContainer_Initialize(container)
container.flowFrames = {};
--Default orientation to horizontal for now.
FlowContainer_SetOrientation(container, "horizontal");
--So far, we haven't actually used any space.
container.flowMaxPrimaryUsed = 0;
container.flowMaxSecondaryUsed = 0;
end
function FlowContainer_PauseUpdates(container)
container.flowPauseUpdates = true;
end
function FlowContainer_ResumeUpdates(container)
container.flowPauseUpdates = false;
FlowContainer_DoLayout(container);
end
function FlowContainer_SetOrientation(container, orientation) --"vertical" or "horizontal". This is the direction it tries to fill first.
container.flowOrientation = orientation;
FlowContainer_DoLayout(container);
end
function FlowContainer_SetMaxPerLine(container, maxPerLine) --nil means we fit as much as possible.
container.flowMaxPerLine = maxPerLine;
FlowContainer_DoLayout(container);
end
function FlowContainer_SetHorizontalSpacing(container, horizontalSpacing)
container.flowHorizontalSpacing = horizontalSpacing;
FlowContainer_DoLayout(container);
end
function FlowContainer_SetVerticalSpacing(container, verticalSpacing)
container.flowVerticalSpacing = verticalSpacing;
FlowContainer_DoLayout(container);
end
function FlowContainer_AddObject(container, object)
tinsert(container.flowFrames, object); --Do we want to let people choose where it will appear?
FlowContainer_DoLayout(container);
end
function FlowContainer_AddLineBreak(container)
tinsert(container.flowFrames, "linebreak");
FlowContainer_DoLayout(container);
end
function FlowContainer_AddSpacer(container, spacerSize)
tinsert(container.flowFrames, spacerSize);
FlowContainer_DoLayout(container);
end
function FlowContainer_BeginAtomicAdd(container)
tinsert(container.flowFrames, "beginatomic");
FlowContainer_DoLayout(container);
end
function FlowContainer_EndAtomicAdd(container)
tinsert(container.flowFrames, "endatomic");
FlowContainer_DoLayout(container);
end
function FlowContainer_RemoveObject(container, object)
assert(type(object) == "table"); --For now, Remove can't be used with non-widgets.
for i=1, #container.flowFrames do --Used instead of tRemoveItem to eliminate dependencies.
if ( container.flowFrames[i] == object ) then
tremove(container.flowFrames, i);
break;
end
end
FlowContainer_DoLayout(container);
end
function FlowContainer_RemoveAllObjects(container)
container.flowFrames = {}; --GC no worse than a table.wipe here.
end
function FlowContainer_GetUsedBounds(container) --Return x, y
if ( container.flowOrientation == "horizontal" ) then
return container.flowMaxSecondaryUsed, container.flowMaxPrimaryUsed;
else
return container.flowMaxPrimaryUsed, container.flowMaxSecondaryUsed;
end
end
function FlowContainer_SetStartingOffset(container, xOffset, yOffset)
if ( container.flowOrientation == "horizontal" ) then
container.startingSecondaryOffset = xOffset;
container.startingPrimaryOffset = -yOffset;
else
container.startingSecondaryOffset = -yOffset;
container.startingPrimaryOffset = xOffset;
end
end
--:GetWidth() and :GetHeight() are used in this function. --meta-comment: Comment added in case anyone ever searches all files for these.
function FlowContainer_DoLayout(container)
if ( container.flowPauseUpdates ) then
return;
end
local primaryDirection, secondaryDirection;
local primarySpacing, secondarySpacing;
if ( container.flowOrientation == "horizontal" ) then
primaryDirection = "Width";
secondaryDirection = "Height";
primarySpacing = container.flowHorizontalSpacing or 0;
secondarySpacing = container.flowVerticalSpacing or 0;
else
primaryDirection = "Height";
secondaryDirection = "Width";
primarySpacing = container.flowVerticalSpacing or 0;
secondarySpacing = container.flowHorizontalSpacing or 0;
end
--To make things easier to understand, I'll comment this as if it was horizontal. To see the vertical comments, just turn your head 90 degrees.
local currentSecondaryLine, currentPrimaryLine = 1, 1;
local currentSecondaryOffset, currentPrimaryOffset = container.startingSecondaryOffset or 0, container.startingPrimaryOffset or 0;
local lineMaxSize = 0;
local maxSecondaryOffset = 0;
local atomicAddStart = nil;
local atomicAtBeginning = nil;
local i = 1;
while ( i <= #container.flowFrames ) do
local object = container.flowFrames[i];
local doContinue = false;
--If it doesn't fit on the current row, move to the next.
if ( object == "linebreak" or --Force a new line
type(object) == "table" and --Make sure this is an actual object before checking further.
((container.flowMaxPerLine and currentPrimaryLine > container.flowMaxPerLine) or --We went past the max number of columns
currentSecondaryOffset + object["Get"..primaryDirection](object) > container["Get"..primaryDirection](container)) ) then --We went past the max pixel width.
if ( not (atomicAddStart and atomicAtBeginning) ) then --If we're in an atomic add and we started at the beginning of the line, wrapping won't help us
currentSecondaryOffset = 0; --Move back all the way to the left
currentPrimaryLine = 1; --Reset column count
currentPrimaryOffset = currentPrimaryOffset + lineMaxSize + secondarySpacing; --Move down by the size of the biggest object in the last row
currentSecondaryLine = currentSecondaryLine + 1; --Move to the next row.
lineMaxSize = 0;
if ( atomicAddStart ) then
--We wrapped around. So we want to move back to the first item in the atomic add and continue from the position we're leaving off (the new line).
i = atomicAddStart;
atomicAtBeginning = true;
doContinue = true;
end
end
end
if ( not doContinue ) then
local objectType = type(object);
if ( objectType == "table" ) then --This is an actual frame
--Did we completely run out of room? Assert for now. --Scratch that, we're just going to keep growing. When we have time, we'll probably want a "didn't fit" callback.
--assert(currentPrimaryOffset + object["Get"..secondaryDirection](object) < container["Get"..secondaryDirection](container));
--Add it.
object:ClearAllPoints();
if ( container.flowOrientation == "horizontal" ) then
object:SetPoint("TOPLEFT", container, "TOPLEFT", currentSecondaryOffset, -currentPrimaryOffset);
else
object:SetPoint("TOPLEFT", container, "TOPLEFT", currentPrimaryOffset, -currentSecondaryOffset);
end
currentSecondaryOffset = currentSecondaryOffset + object["Get"..primaryDirection](object) + primarySpacing;
if ( not atomicAddStart ) then --If we're in the middle of an atomic add, we'll save off the last part when we finish the add.
maxSecondaryOffset = max(maxSecondaryOffset, currentSecondaryOffset);
end
currentPrimaryLine = currentPrimaryLine + 1;
lineMaxSize = max(lineMaxSize, object["Get"..secondaryDirection](object));
elseif ( objectType == "number" ) then --This is a spacer.
currentSecondaryOffset = currentSecondaryOffset + object;
elseif ( objectType == "string" ) then
if ( object == "beginatomic" ) then
if ( currentSecondaryOffset == 0 ) then
atomicAtBeginning = true; --If we're already at the top, we don't want to move anything to the next row. (There's no way it would help.)
end
atomicAddStart = i + 1;
elseif ( object == "endatomic" ) then
maxSecondaryOffset = max(maxSecondaryOffset, currentSecondaryOffset); --We weren't updating the max offset while in an atomic add, so we have to do it now.
atomicAddStart = nil;
atomicAtBeginning = nil;
end
end
i = i + 1;
end
end
--Save off how much we actually used.
container.flowMaxPrimaryUsed = currentPrimaryOffset + lineMaxSize;
container.flowMaxSecondaryUsed = maxSecondaryOffset;
end