After searching a little, I didn't find anything that could do this. So I wrote it myself, and decided I'd share it with the community in case anyone else needed something like this. The code snippet is pretty simple, and should plug in easily to any project (it only uses core irrlicht).
Enums (these should be straightforward, just add them to the top of your file):
Code: Select all
enum ALIGNMENT_Y {L_MIDDLE = 0, L_TOP = 1, L_BOTTOM = -1};
enum ALIGNMENT_X {L_CENTER = 0, L_LEFT = 1, L_RIGHT = -1};
Code: Select all
class SimpleLayout {
private:
s32 lineHeight, curx, cury, maxx, padX, padY;
ALIGNMENT_Y alignment;
public:
SimpleLayout(const rect<s32>& bounds, ALIGNMENT_Y align=L_TOP, s32 px=5, s32 py=5) {
padX = px; padY = py; alignment = align; lineHeight = 0;
maxx = bounds.LowerRightCorner.X-bounds.UpperLeftCorner.X-padX;
s32 maxy = bounds.LowerRightCorner.Y-bounds.UpperLeftCorner.Y-padY;
curx = padX; cury = ((alignment == L_TOP)?padY:maxy);
}
rect<s32> getBounds(s32 width, s32 height){
if(width <= 0){if(maxx-curx+width < 0) newLine(); width = maxx-curx+width;}
width = min(maxx-padX, width);
if(width > maxx-curx) newLine();
lineHeight = max(height, lineHeight);
rect<s32> ret(curx, cury-((alignment == L_BOTTOM)?height:0), curx+width, cury+((alignment == L_TOP)?height:0));
curx += width+padX;
return ret;
}
inline void newLine(s32 height=0){
curx = padX; cury += alignment*(max(height, lineHeight)+padY);
lineHeight = 0;
}
inline s32 getWidth() const {return maxx-padX;}
inline s32 getPadX() const {return padX;}
inline s32 getPadY() const {return padY;}
};
The flow layout places elements side by side, left to right on a line until an element will not fit on the line. It then overflows to the next line, which is lineHeight pixels above or below the previous line (where lineHeight is the maximum height of any element in that line). Any element of width larger than the parent's width is clamped to the parent's width.
You construct it with the bounds of the parent element you wish to add elements to. It only uses the height and width of these bounds, so you can use the absolute or relative bounds. The align parameter can be L_TOP or L_BOTTOM, and determines whether elements are added to the top of the parent and flow down (L_TOP), or from the bottom of the parent and flow up (L_BOTTOM). Px and py define the x and y padding between the edges and each element.
The main function you will call is getBounds. It will "place" an width x height element into a line, and return the relative bounds of the placed element. If width is less than or equal to 0, then it is set to the width of the parent minus the specified width.
The newLine function will end the current line. It will increment the height by at least the height parameter passed [max(height, lineHeight)].
Here's a small example of how to use it:
Code: Select all
IGUIEnvironment* gui = device->getGUIEnvironment();
IGUIElement* root = gui->getRootGUIElement();
SimpleLayout layout(root->getAbsolutePosition(), L_BOTTOM);
gui->addButton(layout.getBounds(70, 20), root, -1, L"Button 1");
gui->addButton(layout.getBounds(0, 20), root, -1, L"Button 2");
gui->addButton(layout.getBounds(70, 20), root, -1, L"Button 3");
gui->addButton(layout.getBounds(0, 20), root, -1, L"Button 4");
I also wrote a GridLayout class, which allocates blocks using a simplelayout that fit in a grid. It mixes well with SimpleLayout, as it calls getBounds from a SimpleLayout object.
Code: Select all
class GridLayout {
private:
SimpleLayout& layout;
s32 cols, rowWidth, cellHeight;
public:
GridLayout(SimpleLayout& l, s32 c, s32 cell_height) : layout(l){
cols = c;
rowWidth = layout.getWidth()-(cols-1)*layout.getPadX();
cellHeight = cell_height;
}
rect<s32> getCell(){return getCell(1.f/cols);}
rect<s32> getCell(f32 per){return layout.getBounds((s32)(rowWidth*per), cellHeight);}
rect<s32> getCell(s32 width, s32 height, ALIGNMENT_X xalign=L_CENTER, ALIGNMENT_Y yalign=L_MIDDLE){
return getCell(1.f/cols, width, height, xalign, yalign);
}
rect<s32> getCell(f32 per, s32 width, s32 height, ALIGNMENT_X xalign=L_CENTER, ALIGNMENT_Y yalign=L_MIDDLE){
s32 cellWidth = (s32)(rowWidth*per);
if(width <= 0) width = max(0, cellWidth+width);
if(height <= 0) height = max(0, cellHeight+height);
rect<s32> ret = layout.getBounds(cellWidth, cellHeight);
s32 dx = max(0, cellWidth-width), dy = max(0, cellHeight-height);
if(xalign == L_LEFT) ret.LowerRightCorner.X -= dx;
else if(xalign == L_RIGHT) ret.UpperLeftCorner.X += dx;
else{
s32 half = dx/2;
ret.LowerRightCorner.X -= half;
ret.UpperLeftCorner.X += half;
}
if(yalign == L_TOP) ret.LowerRightCorner.Y -= dy;
else if(yalign == L_BOTTOM) ret.UpperLeftCorner.Y += dy;
else{
s32 half = dy/2;
ret.LowerRightCorner.Y -= half;
ret.UpperLeftCorner.Y += half;
}
return ret;
}
};
getCell() - returns the bounds of the next cell, has the height used in the constructor, and parent width / number of columns width.
getCell(f32 per) - same as getCell, but returns a cell that is some percent of the full width instead of a uniform 1 / number of columns. These should add up to 1 in a row.
getCell(s32 width, s32 height, ALIGNMENT_X xalign, ALIGNMENT_Y yalign) - allocates the full size of the next cell, but returns a rectangle that is width x height aligned inside the next cell by the xalign and yalign parameters. The final override is the same as this one, just includes the per parameter that changes the size of the cell.
That's everything you need to know to use it. Hopefully it helps you as much as it has helped me. It could be easier to use, like Java's LayoutManagers, but that would require building it in to the GUI framework and recompiling the whole source, and also requiring a getPreferredSize for each GUI element, so not as easy to add to a project. Let me know if you have any questions using this script, and feel free to post improvements to it.
And yes, this code is free to use/modify in any project. Use at your own risk, if it breaks something, it's not my fault (insert permissive open source license here).