forked from Mirrors/openclonk
Script GUI: added TightGridLayout style (requirement of #1842)
The TightGridLayout fills spaces more aggressively. This is slower but makes for a tighter layout. Finding the best layout is NP-complete. This here is just O(N^2) or so.directional-lights
parent
7aeec3279c
commit
8a8593e0ba
|
@ -178,6 +178,10 @@
|
|||
<col>GUI_GridLayout</col>
|
||||
<col>The sub-windows of this window will automatically be arranged in a grid.</col>
|
||||
</row>
|
||||
<row>
|
||||
<col>GUI_TightGridLayout</col>
|
||||
<col>Like GUI_GridLayout but might reorder items and lead to less empty space between sub-windows.</col>
|
||||
</row>
|
||||
<row>
|
||||
<col>GUI_VerticalLayout</col>
|
||||
<col>The sub-windows of this window will automatically be arranged vertically in a list.</col>
|
||||
|
|
|
@ -107,6 +107,7 @@ func StartMenu(plr)
|
|||
menu->AddItem(Clonk, "Test Multiple Windows (Player List)", nil, Scenario, "StartPlayerListTest", "Shows how to display a permanent info dialogue.");
|
||||
menu->AddItem(Lorry, "Tests Two Grid Menus (Trade Menu)", nil, Scenario, "StartTransferTest", "Shows how to work with two grid menus.");
|
||||
menu->AddItem(Sproutberry, "Test HP Bars (HP Bars!)", nil, Scenario, "StartHPBarTest", "HP BARS!!!");
|
||||
menu->AddItem(Crate, "Test mixed grid layout.", nil, Scenario, "StartMixedGridTest", "Grid menu with differently-sized items.");
|
||||
|
||||
active_menu = GuiOpen(main_menu);
|
||||
}
|
||||
|
@ -459,4 +460,39 @@ func OnHPBarClose()
|
|||
RemoveEffect("FoolAroundWithHPBar");
|
||||
HP_bar_menu = nil;
|
||||
Log("HP bar off!");
|
||||
}
|
||||
|
||||
/* ------------------- mixed grid menu test ---------------*/
|
||||
func StartMixedGridTest()
|
||||
{
|
||||
GuiClose(active_menu);
|
||||
|
||||
var menu =
|
||||
{
|
||||
toptext = {Bottom = "2em", Text="GUI_TightGridLayout", Style = GUI_TextHCenter},
|
||||
top = {Top = "2em", Bottom = "50%", Margin = "0.5em", Style = GUI_TightGridLayout, Decoration = GUI_MenuDeco},
|
||||
bottomtext = {Top = "50%", Bottom = "50% + 2em", Text="GUI_GridLayout", Style = GUI_TextHCenter},
|
||||
bottom = {Top = "50% + 2em", Margin = "0.5em", Style = GUI_GridLayout, Decoration = GUI_MenuDeco},
|
||||
BackgroundColor = RGBa(0, 0, 0, 128),
|
||||
};
|
||||
GuiAddCloseButton(menu, Scenario, "CloseCurrentMenu");
|
||||
|
||||
var seed = 1;
|
||||
for (var i=0; i < 50; ++i)
|
||||
{
|
||||
seed = ((seed * 17) + 7) % 1357;
|
||||
var w = (seed % 4) + 1;
|
||||
seed = ((seed * 23) + 13)% 1357;
|
||||
var h = (seed % 4) + 1;
|
||||
var item =
|
||||
{
|
||||
Right = Format("%dem", w),
|
||||
Bottom = Format("%dem", h),
|
||||
Priority = i+1,
|
||||
BackgroundColor = HSL(seed % 255, 200, 200)
|
||||
};
|
||||
GuiAddSubwindow(item, menu.top);
|
||||
GuiAddSubwindow(item, menu.bottom);
|
||||
}
|
||||
active_menu = GuiOpen(menu);
|
||||
}
|
|
@ -3039,6 +3039,7 @@ C4ScriptConstDef C4ScriptGameConstMap[]=
|
|||
{ "GUI_SetTag" ,C4V_Int, C4ScriptGuiWindowActionID::SetTag },
|
||||
{ "GUI_Call" ,C4V_Int, C4ScriptGuiWindowActionID::Call },
|
||||
{ "GUI_GridLayout" ,C4V_Int, C4ScriptGuiWindowStyleFlag::GridLayout },
|
||||
{ "GUI_TightGridLayout" ,C4V_Int, C4ScriptGuiWindowStyleFlag::TightGridLayout },
|
||||
{ "GUI_VerticalLayout" ,C4V_Int, C4ScriptGuiWindowStyleFlag::VerticalLayout },
|
||||
{ "GUI_TextVCenter" ,C4V_Int, C4ScriptGuiWindowStyleFlag::TextVCenter },
|
||||
{ "GUI_TextHCenter" ,C4V_Int, C4ScriptGuiWindowStyleFlag::TextHCenter },
|
||||
|
|
|
@ -1445,6 +1445,91 @@ void C4ScriptGuiWindow::UpdateLayoutGrid()
|
|||
EnableScrollBar(lowestChildRelY > height, lowestChildRelY);
|
||||
}
|
||||
|
||||
// Similar to the grid layout but tries to fill spaces more thoroughly.
|
||||
// It's slower and might reorder items.
|
||||
void C4ScriptGuiWindow::UpdateLayoutTightGrid()
|
||||
{
|
||||
const int32_t &width = rcBounds.Wdt;
|
||||
const int32_t &height = rcBounds.Hgt;
|
||||
const int32_t borderX(0), borderY(0);
|
||||
int32_t lowestChildRelY = 0;
|
||||
|
||||
std::list<C4ScriptGuiWindow*> alreadyPlacedChildren;
|
||||
|
||||
for (C4GUI::Element * element : *this)
|
||||
{
|
||||
C4ScriptGuiWindow *child = static_cast<C4ScriptGuiWindow*>(element);
|
||||
// calculate the space the child needs, correctly respecting the margins
|
||||
const float childLeftMargin = child->CalculateRelativeSize(width, leftMargin, relLeftMargin);
|
||||
const float childTopMargin = child->CalculateRelativeSize(height, topMargin, relTopMargin);
|
||||
const float childRightMargin = child->CalculateRelativeSize(width, rightMargin, relRightMargin);
|
||||
const float childBottomMargin = child->CalculateRelativeSize(height, bottomMargin, relBottomMargin);
|
||||
|
||||
const float childWdtF = float(child->rcBounds.Wdt) + childLeftMargin + childRightMargin;
|
||||
const float childHgtF = float(child->rcBounds.Hgt) + childTopMargin + childBottomMargin;
|
||||
|
||||
// do all the rounding after the calculations
|
||||
const int32_t childWdt = (int32_t)(childWdtF + 0.5f);
|
||||
const int32_t childHgt = (int32_t)(childHgtF + 0.5f);
|
||||
|
||||
// Look for a free spot.
|
||||
int32_t currentX = borderX;
|
||||
int32_t currentY = borderY;
|
||||
|
||||
bool hadOverlap = false;
|
||||
int overlapRepeats = 0;
|
||||
do
|
||||
{
|
||||
auto overlapsWithOther = [¤tX, ¤tY, &childWdt, &childHgt](C4ScriptGuiWindow *other)
|
||||
{
|
||||
if (currentX + childWdt <= other->rcBounds.x) return false;
|
||||
if (currentY + childHgt <= other->rcBounds.y) return false;
|
||||
if (currentX >= other->rcBounds.GetRight()) return false;
|
||||
if (currentY >= other->rcBounds.GetBottom()) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
int32_t currentMinY = 0;
|
||||
hadOverlap = false;
|
||||
for (auto &other : alreadyPlacedChildren)
|
||||
{
|
||||
// Check if the other element is not yet above the new child.
|
||||
if ((other->rcBounds.GetBottom() > currentY) && other->rcBounds.Hgt > 0)
|
||||
{
|
||||
if (currentMinY == 0 || (other->rcBounds.GetBottom() < currentMinY))
|
||||
currentMinY = other->rcBounds.GetBottom();
|
||||
}
|
||||
// If overlapping, we must advance.
|
||||
if (overlapsWithOther(other))
|
||||
{
|
||||
hadOverlap = true;
|
||||
currentX = other->rcBounds.GetRight();
|
||||
// Break line if the element doesn't fit anymore.
|
||||
if (currentX + childWdt > width)
|
||||
{
|
||||
currentX = borderX;
|
||||
// Start forcing change once we start repeating the check. Otherwise, there might
|
||||
// be a composition of children that lead to infinite loop. The worst-case number
|
||||
// of sensible checks might O(N^2) be with a really unfortunate children list.
|
||||
const int32_t forcedMinimalChange = (overlapRepeats > alreadyPlacedChildren.size()) ? 1 : 0;
|
||||
currentY = std::max(currentY + forcedMinimalChange, currentMinY);
|
||||
}
|
||||
}
|
||||
}
|
||||
overlapRepeats += 1;
|
||||
} while (hadOverlap);
|
||||
|
||||
alreadyPlacedChildren.push_back(child);
|
||||
|
||||
lowestChildRelY = std::max(lowestChildRelY, currentY + childHgt);
|
||||
child->rcBounds.x = currentX + static_cast<int32_t>(childLeftMargin);
|
||||
child->rcBounds.y = currentY + static_cast<int32_t>(childTopMargin);
|
||||
}
|
||||
|
||||
// do we need a scroll bar?
|
||||
EnableScrollBar(lowestChildRelY > height, lowestChildRelY);
|
||||
}
|
||||
|
||||
void C4ScriptGuiWindow::UpdateLayoutVertical()
|
||||
{
|
||||
const int32_t borderY(0);
|
||||
|
@ -1738,6 +1823,8 @@ bool C4ScriptGuiWindow::UpdateLayout(C4TargetFacet &cgo, float parentWidth, floa
|
|||
// special layout selected?
|
||||
if (style & C4ScriptGuiWindowStyleFlag::GridLayout)
|
||||
UpdateLayoutGrid();
|
||||
else if (style & C4ScriptGuiWindowStyleFlag::TightGridLayout)
|
||||
UpdateLayoutTightGrid();
|
||||
else if (style & C4ScriptGuiWindowStyleFlag::VerticalLayout)
|
||||
UpdateLayoutVertical();
|
||||
|
||||
|
|
|
@ -82,7 +82,8 @@ enum C4ScriptGuiWindowStyleFlag
|
|||
FitChildren = 64,
|
||||
Multiple = 128,
|
||||
IgnoreMouse = 256,
|
||||
NoCrop = 512
|
||||
NoCrop = 512,
|
||||
TightGridLayout = 1024,
|
||||
};
|
||||
|
||||
class C4ScriptGuiWindow;
|
||||
|
@ -291,6 +292,7 @@ public:
|
|||
bool UpdateChildLayout(C4TargetFacet &cgo, float parentWidth, float parentHeight);
|
||||
// special layouts that are set by styles
|
||||
void UpdateLayoutGrid();
|
||||
void UpdateLayoutTightGrid();
|
||||
void UpdateLayoutVertical();
|
||||
// the window will be drawn in the context of a viewport BY the viewport
|
||||
// so just do nothing when TheScreen wants to draw the window
|
||||
|
|
Loading…
Reference in New Issue