Difference between revisions of "GridLayout"

From CEGUI Wiki - Crazy Eddie's GUI System (Open Source)
Jump to: navigation, search
(initial)
 
m (Robot: Cosmetic changes)
 
(7 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 +
{{VersionBadge|0.6}}
 +
 
== Introduction ==
 
== Introduction ==
This class provides a layout manager for CEGUI that lays out a given window according to an invisible grid laid over the window's client area.
+
It is hard to hard-code a layout that is constantly looking good with different screen resolutions and for different window sizes - especially when dealing with resizable windows.
 +
Often, two wdgets are too close together or too far apart. The GridLayout class solves such problems:
 +
This class provides a layout manager for CEGUI that lays out a given window according to an invisible grid laid over the window's client area. The size of the grid's cells is changed according to
 +
some parameters when the window size changes. The widgets, the client application registers with the layout manager, stick to specific cells and change their size when the cells do. This concept (inspired by Trolltechs Qt) allows for many different and complex window layouts.
 
The header file is documented with doxygen tags.
 
The header file is documented with doxygen tags.
  
Questions, remarks, blame, etc. shall be posted within the GridLayout forum thread.
+
Questions, remarks, comments, blame, etc. shall be posted within the [http://www.cegui.org.uk/phpBB2/viewtopic.php?p=12343#12343 GridLayout forum thread].
 
+
== Files ==
+
=== GridLayout.h ===
+
<code><cpp/>
+
/*
+
Copyright (c) 2007 Roland Wirth
+
 
+
Permission is hereby granted, free of charge, to any person obtaining a copy
+
of this software and associated documentation files (the "Software"), to deal
+
in the Software without restriction, including without limitation the rights
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+
copies of the Software, and to permit persons to whom the Software is
+
furnished to do so, subject to the following conditions:
+
 
+
The above copyright notice and this permission notice shall be included in
+
all copies or substantial portions of the Software.
+
 
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+
THE SOFTWARE.
+
*/
+
 
+
#ifndef GRIDLAYOUT_H_INCLUDED_______
+
#define GRIDLAYOUT_H_INCLUDED_______
+
 
+
#include <CEGUI/CEGUIWindow.h>
+
 
+
/**
+
* \brief Implements a layout manager that lays out a window's children according to a grid.
+
* \author Roland Wirth
+
*
+
* This class lays out a window's children. It does so by overlaying the window's client area with an imaginary grid,
+
* whose cell parameters may be defined by the using application. Each row and column of this grid may be assigned a
+
* minimum height/width and a so-called stretch factor. This factor comes into play if there is still unused space
+
* after the subtraction of each element's minimum height/width. The stretch factor is the key according to which the
+
* remaining space is distributed among the rows/columns. After calculating the grid, each child is repositioned and
+
* resized according to the underlying cell positions and sizes.
+
* Every child is assigned a start cell coordinate and the number of cells it spans in horizontal and vertical direction.
+
* New rows/columns are added automatically as soon as there is a child using it or the user defines a minimum size
+
* or a stretch factor for it.
+
*
+
* \bug Unused rows/columns are not removed after removing all the children that used them.
+
*
+
* Example:
+
* The following example creates a FrameWindow with five PushButtons. These will be aligned this way:
+
* <nowiki><pre></nowiki>
+
* x|<-----0----->|<--1-->|<--2-->|
+
* -+---------------------+-------+
+
* 0|                    |  2  |
+
* -|                    +-------+
+
* 1|          1          |  3  |
+
* -|                    +-------+
+
* 2|                    |      |
+
* -+-------------+-------+-------+
+
* 3|            |  4  |  5  |
+
* -+-------------+-------+-------+<nowiki></pre></nowiki>
+
* Rows 0,1 and 3 and the last two columns will not change size if you change the window's size.
+
*
+
* \code
+
* // create a new window and its layout manager
+
* CEGUI::Window *wnd = (CEGUI::FrameWindow *)CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/FrameWindow", (CEGUI::utf8*)"MyWindow");
+
* GridLayout *layout = new GridLayout(wnd, true); // setting the second parameter to true will
+
*                                                // cause self-destruction of the layout manager on destruction of the window
+
*
+
* CEGUI::Window *btn;
+
* btn = CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/Button", (CEGUI::utf8*)"Btn1"); btn->setText("1");
+
* layout->addChildWindow(btn, 0, 0, 3, 3, UDim(1.0/512, 0.0));
+
*
+
* btn = CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/Button", (CEGUI::utf8*)"Btn1"); btn->setText("2");
+
* btn->setMinSize(CEGUI::UVector2(UDim(0.06, 0.0), UDim(0.03, 0.0)));
+
* layout->addChildWindow(btn, 2, 0, 1, 1, UDim(1.0/512, 0.0));
+
*
+
* btn = CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/Button", (CEGUI::utf8*)"Btn1"); btn->setText("3");
+
* btn->setMinSize(CEGUI::UVector2(UDim(0.06, 0.0), UDim(0.03, 0.0)));
+
* layout->addChildWindow(btn, 2, 1, 1, 1, UDim(1.0/512, 0.0));
+
*
+
* btn = CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/Button", (CEGUI::utf8*)"Btn1"); btn->setText("4");
+
* btn->setMinSize(CEGUI::UVector2(UDim(0.06, 0.0), UDim(0.03, 0.0)));
+
* layout->addChildWindow(btn, 1, 3, 1, 1, UDim(1.0/512, 0.0));
+
*
+
* btn = CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/Button", (CEGUI::utf8*)"Btn1"); btn->setText("5");
+
* btn->setMinSize(CEGUI::UVector2(UDim(0.06, 0.0), UDim(0.03, 0.0)));
+
* layout->addChildWindow(btn, 2, 3, 1, 1, UDim(1.0/512, 0.0));
+
*
+
* layout->setRowStretch(0, 0);
+
* layout->setRowStretch(1, 0);
+
* layout->setRowStretch(3, 0);
+
* layout->setColStretch(1, 0);
+
* layout->setColStretch(2, 0);
+
*
+
* layout->setOuterMargin(UDim(1.0/256, 0.0)); // adds a margin to the window's whole client area
+
* wnd->setMinSize(layout->getMinSize());      // asks the layout manager for the window's minimum size
+
* wnd->setSize(wnd->getMinSize());
+
* layout->layout();                          // forces an initial layout
+
* \endcode
+
*/
+
class GridLayout {
+
public:
+
/** \brief Helper enum to describe the horizontal alignment of a child */
+
enum HAlignment {
+
AlignHCenter, //!< The child will be centered horizonally in its area
+
AlignLeft,    //!< The child will be left-aligned in its area
+
AlignRight    //!< The child will be right-aligned in its area
+
};
+
+
/** \brief Helper enum to describe the vertical alignment of a child */
+
enum VAlignment {
+
AlignVCenter, //!< The child will be centered vertically in its area
+
AlignTop,    //!< The child will be top-aligned in its area
+
AlignBottom  //!< The child will be bottom-aligned in its area
+
};
+
 
+
/**
+
* \brief Structure containing the pieces of information to lay out the window's children.
+
*
+
* The Constraints struct contains all the information necessary for the layout manager to
+
* layout the child element described by its \p child member.
+
*/
+
struct Constraints {
+
CEGUI::Window *child; //!< The child window the given information applies to.
+
unsigned cx; //!< The child's horizontal start cell index. This index is zero-based.
+
unsigned cy; //!< The child's vertical start cell index. This index is zero-based.
+
unsigned cwidth; //!< The child's horizontal extent, measured in cells.
+
unsigned cheight; //!< The child's vertical extent, measured in cells.
+
HAlignment halign; //!< Describes the child's horizontal alignment if its cell is bigger than its maximum width or hfill is false.
+
VAlignment valign; //!< Describes the child's vertical alignment if its cell is bigger than its maximum height or vfill is false.
+
bool hfill; //!< If this flag is set, the child will not have a width bigger than its minimum width.
+
bool vfill; //!< If this flag is set, the child will not have a height bigger than its minimum height.
+
CEGUI::UDim margin; //!< The margin that will be left free around the child. The relative component of the CEGUI::UDim is relative to the screen width.
+
};
+
 
+
/**
+
* \brief Constructs a GridLayout.
+
*
+
* Constructs a GridLayout object for the purpose of laying out the window pointed to by \p laywnd,
+
* optionally deleting itself when \p laywnd gets destroyed. Also connects the constructed object to the relevant window events.
+
* \param[in] laywnd          The window that shall be laid out
+
* \param[in] deleteondestroy If this flag is set, the GridLayout object will \c delete itself when the layout window gets destroyed.
+
*/
+
GridLayout(CEGUI::Window *laywnd, bool deleteondestroy=true);
+
~GridLayout();
+
 
+
/**
+
* \brief adds a child window to the layout
+
*
+
* This is an overloaded member function provided for convenience. It just calls addChildWindow(const Constraints& constrs) with a Constraints struct filled with the given parameters.
+
* \returns \c true on success, \c false otherwise
+
*
+
* \sa addChildWindow(const Constraints& constrs),
+
*    Constraints
+
*/
+
bool addChildWindow(CEGUI::Window *wnd, unsigned cx, unsigned cy, unsigned cwidth=1, unsigned cheight=1, CEGUI::UDim margin=CEGUI::UDim(0.0,0.0), bool hfill=true, bool vfill=true, HAlignment halign=AlignHCenter, VAlignment valign=AlignVCenter);
+
 
+
/**
+
* \brief adds a child window to the layout
+
*
+
* Adds the child window parametrised by the \p constrs struct to the list of child windows to lay out.
+
* The window pointed to by the Constraints::child member is automatically added to the children of the window the GridLayout is laying out, if the class user has not already done so.
+
* \param[in] constrs The Constraints struct describing the child window to be added.
+
* \returns \c true on success, \c false otherwise
+
* \sa Constraints
+
*/
+
bool addChildWindow(const Constraints& constrs);
+
void removeChildWindow(const CEGUI::String &name); //!< Removes the child window specified by \p name from the layout and from the window's children.
+
void removeChildWindow(CEGUI::Window *window);    //!< Removes the child window specified by \p window from the layout and from the window's children.
+
void removeChildWindow(unsigned id);              //!< Removes the child window specified by \p id from the layout and from the window's children.
+
+
/**
+
* \brief Sets a row's stretch factor
+
*
+
* Sets the stretch factor for the given row to the given value.
+
* For an explanation of stretch factors, see the GridLayout class description.
+
* If the \p row argument addresses a non-existent row, an appropriate number of rows is created and initialised.
+
* The default stretch factor is 1.
+
* \param[in] row Number of the affected row (zero-based)
+
* \param[in] stretch The stretch factor
+
* \sa GridLayout,
+
*    getRowStretch
+
*/
+
void setRowStretch(unsigned row, unsigned stretch);
+
+
/**
+
* \brief Sets a column's stretch factor
+
*
+
* Sets the stretch factor for the given column to the given value.
+
* For an explanation of stretch factors, see the GridLayout class description.
+
* If the \p column argument addresses a non-existent column, an appropriate number of columns is created and initialised.
+
* The default stretch factor is 1.
+
* \param[in] column Number of the affected column (zero-based)
+
* \param[in] stretch The stretch factor
+
* \sa GridLayout,
+
*    getColStretch
+
*/
+
void setColStretch(unsigned col, unsigned stretch);
+
 
+
/**
+
* \brief Returns the given row's stretch factor
+
* \param[in] row The row whose stretch factor shall be returned
+
* \returns the given row's stretch factor or 1 if the row does not exist
+
* \sa setRowStretch
+
*/
+
unsigned getRowStretch(unsigned row) const;
+
 
+
/**
+
* \brief Returns the given column's stretch factor
+
* \param[in] column The column whose stretch factor shall be returned
+
* \returns the given column's stretch factor or 1 if the column does not exist
+
* \sa setColStretch
+
*/
+
unsigned getColStretch(unsigned col) const;
+
+
/**
+
* \brief Sets a row's minimum height
+
*
+
* This function sets the given row's minimum height to the given height.
+
* The relative component of the CEGUI::UDim is relative to the screen width.
+
* \param[in] row The row whose minimum height shall be set
+
* \param[in] sz The requested minimum height
+
* \sa getRowMinHeight
+
*/
+
void setRowMinHeight(unsigned row, CEGUI::UDim sz);
+
+
/**
+
* \brief Sets a column's minimum width
+
*
+
* This function sets the given column's minimum width to the given width.
+
* The relative component of the CEGUI::UDim is relative to the screen width.
+
* \param[in] column The column whose minimum width shall be set
+
* \param[in] sz The requested minimum width
+
* \sa getColMinWidth
+
*/
+
void setColMinWidth(unsigned col, CEGUI::UDim sz);
+
+
/**
+
* \brief Returns the given row's minimum height
+
* \param[in] row The row whose sminimum height shall be returned
+
* \returns the given row's previously set minimum height or <tt>CEGUI::UDim(0.0, 0.0)</tt> if the row does not exist or the minimum height has not been set
+
* \sa setRowMinHeight
+
*/
+
CEGUI::UDim getRowMinHeight(unsigned row) const;
+
+
/**
+
* \brief Returns the given column's minimum width
+
* \param[in] column The column whose sminimum width shall be returned
+
* \returns the given column's previously set minimum width or <tt>CEGUI::UDim(0.0, 0.0)</tt> if the column does not exist or the minimum width has not been set.
+
* \sa setColMinWidth
+
*/
+
CEGUI::UDim getColMinWidth(unsigned col) const;
+
 
+
/**
+
* \brief Sets the layout's outer margin
+
*
+
* This function sets the amount of space that is subtracted from the windows client area, so the window's
+
* children do not touch any of the borders of the window's client area.
+
* The relative component of the CEGUI::UDim is relative to the screen width.
+
* \param[in] margin The requested margin
+
* \sa getOuterMargin
+
*/
+
void setOuterMargin(CEGUI::UDim margin);
+
 
+
/**
+
* \brief Gets the layout's outer margin
+
*
+
* This function retrieves the amount of space that is subtracted from the windows client area.
+
* \returns The margin previously set by a call to setOuterMargin margin
+
* \sa setOuterMargin
+
*/
+
CEGUI::UDim getOuterMargin() const;
+
 
+
/**
+
* \brief Forces a re-layout
+
*
+
* This function causes an immediate re-layout of the window's children.
+
* \returns true if the layout was successful, false otherwise
+
*/
+
bool layout() const;
+
bool handleSize(const CEGUI::EventArgs& );
+
bool handleShow(const CEGUI::EventArgs& );
+
bool handleHide(const CEGUI::EventArgs& );
+
bool handleWndDestruct(const CEGUI::EventArgs&);
+
+
/**
+
* \brief Returns the minimum window size for the layout to be displayed correctly
+
*
+
* This function calculates the minimum sizes for all children plus additional margins, yielding the absolute minimum size for the layout to be displayed correctly.
+
* \returns The minimum size, converted to a unified CEGUI vector for easy use as a parameter to CEGUI::Window::setMinSize()
+
*/
+
CEGUI::UVector2 getMinSize() const;
+
 
+
private:
+
typedef std::map<unsigned, float> SizeMap;
+
typedef std::map<unsigned, CEGUI::UDim> USizeMap;
+
typedef std::map<unsigned, unsigned> StretchMap;
+
typedef std::vector<Constraints> ChildVector;
+
+
CEGUI::Window *LayoutWnd_;
+
ChildVector Children_;
+
mutable StretchMap RowStretch_; // mutable because operator[] is non-const and I'm too lazy to implement this as (*find(key)).second
+
mutable StretchMap ColStretch_;
+
USizeMap  RowMinSize_;
+
USizeMap  ColMinSize_;
+
 
+
unsigned CellsX_, CellsY_;
+
CEGUI::UDim OuterMargin_;
+
CEGUI::Event::ScopedConnection SizeConn_, DestrConn_, ShowConn_, HideConn_;
+
bool DeleteOnDestroy_;
+
 
+
// CEGUI screws up the layout when hiding a window by firing size events after the hide event when FrameWindow::getUnclippedPixelRect() returns invalid results.
+
// It does, however, not fire a size event before the show event and when the show event is fired, the pixelRect is still invalid.
+
// The workaround is to disable reaction to SizeEvents when a hide event occurs.
+
bool LayoutEnable_;
+
 
+
protected:
+
enum Scope { ScopeRows, ScopeCols };
+
bool doLayout() const;
+
void calcMinCellSizes(Scope scope, SizeMap &sz) const;
+
unsigned calcItemStretchSum(Scope scope, const Constraints &item) const;
+
inline bool isFixedItem(Scope scope, const Constraints &item) const;
+
void calcStretchySizes(Scope scope, float szmax, SizeMap &sz) const;
+
inline CEGUI::Size getSizeBase() const;
+
void updateData(Scope scope);
+
void removeChildWindow_impl(ChildVector::iterator i);
+
};
+
 
+
#endif
+
</code>
+
=== GridLayout.cpp ===
+
<code><cpp/>
+
/*
+
Copyright (c) 2007 Roland Wirth
+
 
+
Permission is hereby granted, free of charge, to any person obtaining a copy
+
of this software and associated documentation files (the "Software"), to deal
+
in the Software without restriction, including without limitation the rights
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+
copies of the Software, and to permit persons to whom the Software is
+
furnished to do so, subject to the following conditions:
+
 
+
The above copyright notice and this permission notice shall be included in
+
all copies or substantial portions of the Software.
+
 
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+
THE SOFTWARE.
+
*/
+
 
+
#include <CEGUI/CEGUISystem.h>
+
#include <CEGUI/CEGUIWindow.h>
+
 
+
#include <vector>
+
#include <map>
+
#include <assert.h>
+
 
+
#include "GridLayout.h"
+
 
+
inline CEGUI::UDim absToUni(const float &val) {
+
return CEGUI::UDim(0.0, val);
+
}
+
 
+
inline CEGUI::UVector2 absToUni(const CEGUI::Vector2 &val) {
+
return CEGUI::UVector2(CEGUI::UDim(0.0, val.d_x), CEGUI::UDim(0.0, val.d_y));
+
}
+
 
+
inline CEGUI::URect absToUni(const CEGUI::Rect &val) {
+
return CEGUI::URect(CEGUI::UDim(0.0, val.d_left), CEGUI::UDim(0.0, val.d_top), CEGUI::UDim(0.0, val.d_right), CEGUI::UDim(0.0, val.d_bottom));
+
}
+
 
+
 
+
GridLayout::GridLayout(CEGUI::Window *laywnd, bool deleteondestroy)
+
: LayoutWnd_ (laywnd)
+
, CellsX_(0)
+
, CellsY_(0)
+
, OuterMargin_(0.0, 0.0)
+
, DeleteOnDestroy_(deleteondestroy)
+
, LayoutEnable_(true)
+
{
+
// Subscribe to size event to update the child layout
+
SizeConn_  = LayoutWnd_->subscribeEvent(CEGUI::Window::EventSized, CEGUI::Event::Subscriber(&GridLayout::handleSize, this));
+
// Subscribe to show event to update the child layout (obviously, CEGUI resizes FrameWindows before hiding, resulting in wrong layout when showing them again)
+
ShowConn_  = LayoutWnd_->subscribeEvent(CEGUI::Window::EventShown, CEGUI::Event::Subscriber(&GridLayout::handleShow, this));
+
HideConn_  = LayoutWnd_->subscribeEvent(CEGUI::Window::EventHidden, CEGUI::Event::Subscriber(&GridLayout::handleHide, this));
+
// Subscribe to delete event to destroy self if applicable
+
DestrConn_ = LayoutWnd_->subscribeEvent(CEGUI::Window::EventDestructionStarted, CEGUI::Event::Subscriber(&GridLayout::handleWndDestruct, this));
+
}
+
 
+
GridLayout::~GridLayout() {
+
}
+
 
+
// convenience function to add a child window
+
bool GridLayout::addChildWindow(CEGUI::Window *wnd, unsigned cx, unsigned cy, unsigned cwidth, unsigned cheight, CEGUI::UDim margin, bool hfill, bool vfill, HAlignment halign, VAlignment valign) {
+
Constraints constrs = { wnd, cx, cy, cwidth, cheight, halign, valign, hfill, vfill, margin };
+
return addChildWindow(constrs);
+
}
+
 
+
// adds the child window described by the constraints struct to the list of layouted items.
+
// Also adds it to the layouted window's children, if the application has not done so already.
+
bool GridLayout::addChildWindow(const Constraints& constrs) {
+
if(constrs.child->getParent() != LayoutWnd_) {
+
LayoutWnd_->addChildWindow(constrs.child);
+
}
+
 
+
Children_.push_back(constrs);
+
// initialize additional cols/rows and set new counts appropriately
+
if(constrs.cx + constrs.cwidth > CellsX_) {
+
CellsX_ = constrs.cx + constrs.cwidth;
+
updateData(ScopeCols);
+
}
+
if(constrs.cy + constrs.cheight > CellsY_) {
+
CellsY_ = constrs.cy + constrs.cheight;
+
updateData(ScopeRows);
+
}
+
 
+
return true;
+
}
+
 
+
void GridLayout::removeChildWindow(const CEGUI::String &name) {
+
for(ChildVector::iterator i = Children_.begin(); i != Children_.end(); ++i) {
+
if(i->child->getName() == name) {
+
removeChildWindow_impl(i);
+
return;
+
}
+
}
+
}
+
 
+
void GridLayout::removeChildWindow(CEGUI::Window *window) {
+
for(ChildVector::iterator i = Children_.begin(); i != Children_.end(); ++i) {
+
if(i->child == window) {
+
removeChildWindow_impl(i);
+
return;
+
}
+
}
+
}
+
 
+
void GridLayout::removeChildWindow(unsigned id) {
+
for(ChildVector::iterator i = Children_.begin(); i != Children_.end(); ++i) {
+
if(i->child->getID() == id) {
+
removeChildWindow_impl(i);
+
return;
+
}
+
}
+
}
+
 
+
// sets a row's stretch factor. Rows with a stretch factor of 0 will not be bigger than the minimum size of its biggest element.
+
void GridLayout::setRowStretch(unsigned row, unsigned stretch) {
+
RowStretch_[row] = stretch;
+
if(row >= CellsY_) { CellsY_ = row + 1; updateData(ScopeRows); }
+
}
+
 
+
unsigned GridLayout::getRowStretch(unsigned row) const {
+
StretchMap::const_iterator i = RowStretch_.find(row);
+
if(i == RowStretch_.end()) return 1;
+
else return i->second;
+
}
+
 
+
// sets a column's stretch factor. Columns with a stretch factor of 0 will not be bigger than the minimum size of its biggest element
+
void GridLayout::setColStretch(unsigned col, unsigned stretch) {
+
ColStretch_[col] = stretch;
+
if(col >= CellsX_) { CellsX_ = col + 1; updateData(ScopeCols); }
+
}
+
 
+
unsigned GridLayout::getColStretch(unsigned col) const {
+
StretchMap::const_iterator i = ColStretch_.find(col);
+
if(i == ColStretch_.end()) return 1;
+
else return i->second;
+
}
+
 
+
// sets a row's minimum height
+
void GridLayout::setRowMinHeight(unsigned row, CEGUI::UDim sz) {
+
RowMinSize_[row] = sz;
+
if(row >= CellsY_) { CellsY_ = row + 1; updateData(ScopeRows); }
+
}
+
 
+
CEGUI::UDim GridLayout::getRowMinHeight(unsigned row) const {
+
USizeMap::const_iterator i = RowMinSize_.find(row);
+
if(i == RowMinSize_.end()) return CEGUI::UDim(0.0, 0.0);
+
else return i->second;
+
}
+
 
+
// sets a column's minimum width
+
void GridLayout::setColMinWidth(unsigned col, CEGUI::UDim sz) {
+
ColMinSize_[col] = sz;
+
if(col >= CellsX_) { CellsX_ = col + 1; updateData(ScopeCols); }
+
}
+
 
+
CEGUI::UDim GridLayout::getColMinWidth(unsigned col) const {
+
USizeMap::const_iterator i = ColMinSize_.find(col);
+
if(i == ColMinSize_.end()) return CEGUI::UDim(0.0, 0.0);
+
else return i->second;
+
}
+
 
+
// Sets the outer margin of the layout (UDim relative to root window width).
+
void GridLayout::setOuterMargin(CEGUI::UDim margin) {
+
OuterMargin_ = margin;
+
}
+
 
+
CEGUI::UDim GridLayout::getOuterMargin() const { return OuterMargin_; }
+
 
+
// forces a relayout of the window's children
+
bool GridLayout::layout() const {
+
return doLayout();
+
}
+
 
+
// relayouts the children if the window changes size
+
bool GridLayout::handleSize(const CEGUI::EventArgs& ) {
+
// printf("[GridLayout Trace] handleSize Wnd=0x%x\n", LayoutWnd_);
+
if(!LayoutEnable_) return true;
+
return doLayout();
+
}
+
 
+
bool GridLayout::handleWndDestruct(const CEGUI::EventArgs&) {
+
if(SizeConn_.isValid()) SizeConn_->disconnect();
+
if(DeleteOnDestroy_)
+
delete this;
+
 
+
return true;
+
}
+
 
+
bool GridLayout::handleShow(const CEGUI::EventArgs& ) {
+
// printf("[GridLayout Trace] handleShow Wnd=0x%x\n", LayoutWnd_);
+
LayoutEnable_ = true;
+
return true;
+
}
+
 
+
bool GridLayout::handleHide(const CEGUI::EventArgs& ) {
+
// printf("[GridLayout Trace] handleHide Wnd=0x%x\n", LayoutWnd_);
+
LayoutEnable_ = false;
+
return true;
+
}
+
 
+
CEGUI::UVector2 GridLayout::getMinSize() const {
+
SizeMap colsz, rowsz;
+
 
+
CEGUI::Rect inner = LayoutWnd_->getUnclippedInnerRect();
+
CEGUI::Rect outer = LayoutWnd_->getUnclippedPixelRect();
+
CEGUI::Size sizeBase(getSizeBase());
+
 
+
inner.offset(CEGUI::Vector2(0.0,0.0) - outer.getPosition());
+
 
+
// fill default stretch values and determine size for non-stretching columns; also determines the base for stretch calculations
+
float colsum=0, rowsum=0;
+
unsigned xstretchsum=0, ystretchsum=0;
+
calcMinCellSizes(ScopeCols, colsz);
+
calcMinCellSizes(ScopeRows, rowsz);
+
 
+
// sum up sizes and add outer margin
+
float outermargin = OuterMargin_.asAbsolute(sizeBase.d_width);
+
float totalwidth = 2*outermargin, totalheight=2*outermargin;
+
for(unsigned i = 0; i < CellsX_; ++i) {
+
totalwidth += colsz[i];
+
}
+
for(unsigned i = 0; i < CellsY_; ++i) {
+
totalheight += rowsz[i];
+
}
+
 
+
// add offset size from client area to the window itself
+
totalwidth  += outer.getWidth()  - inner.getWidth();
+
totalheight += outer.getHeight() - inner.getHeight();
+
return CEGUI::UVector2(CEGUI::UDim(0.0f, totalwidth), CEGUI::UDim(0.0f, totalheight));
+
}
+
 
+
bool GridLayout::doLayout() const {
+
SizeMap colsz, rowsz;
+
 
+
CEGUI::Rect inner = LayoutWnd_->getUnclippedInnerRect();
+
CEGUI::Rect outer = LayoutWnd_->getUnclippedPixelRect();
+
 
+
CEGUI::Size sizeBase(getSizeBase());
+
float outermargin = OuterMargin_.asAbsolute(sizeBase.d_width);
+
 
+
inner.offset(CEGUI::Vector2(0.0,0.0) - outer.getPosition());
+
inner.d_top    += outermargin;
+
inner.d_left  += outermargin;
+
inner.d_bottom -= outermargin;
+
inner.d_right  -= outermargin;
+
 
+
// Calculate the minimum row/col sizes
+
calcMinCellSizes(ScopeCols, colsz);
+
calcMinCellSizes(ScopeRows, rowsz);
+
 
+
// calculate the stretchable rows/columns based on their stretch value
+
calcStretchySizes(ScopeCols, inner.getWidth(),  colsz);
+
calcStretchySizes(ScopeRows, inner.getHeight(), rowsz);
+
 
+
for(ChildVector::const_iterator i = Children_.begin(); i != Children_.end(); ++i) {
+
// calculate item area
+
float xoff=0, yoff=0, width=0, height=0;
+
for(unsigned k = 0; k < i->cx; ++k) xoff += colsz[k];
+
for(unsigned k = 0; k < i->cy; ++k) yoff += rowsz[k];
+
for(unsigned k = 0; k < i->cwidth; ++k) width += colsz[i->cx+k];
+
for(unsigned k = 0; k < i->cheight; ++k) height += rowsz[i->cy+k];
+
 
+
// retrieve margin relative to root window width
+
float margin = i->margin.asAbsolute(sizeBase.d_width);
+
 
+
xoff  += margin;
+
yoff  += margin;
+
width  -= 2*margin;
+
height -= 2*margin;
+
 
+
if(width < 0) width = 0;
+
if(height < 0) height = 0;
+
 
+
CEGUI::Rect area(xoff, yoff, xoff + width, yoff + height);
+
CEGUI::Rect effective(area);
+
CEGUI::Size maxsize = i->child->getMaxSize().asAbsolute(sizeBase).asSize();
+
CEGUI::Size minsize = i->child->getMinSize().asAbsolute(sizeBase).asSize();
+
 
+
// if area is bigger than maximum control size or no scaling is requested, position it according to the align parameter
+
CEGUI::Vector2 offset(0.0, 0.0);
+
if(maxsize.d_width < area.getWidth() || !i->hfill) {
+
if(i->hfill) { // area too big
+
effective.setWidth(maxsize.d_width);
+
} else { // scaling not requested
+
effective.setWidth(minsize.d_width);
+
}
+
 
+
switch(i->halign) {
+
case AlignLeft:
+
offset.d_x = 0.0f;
+
break;
+
case AlignHCenter:
+
offset.d_x = (area.getWidth() - effective.getWidth())/2;
+
break;
+
case AlignRight:
+
offset.d_x = area.getWidth() - effective.getWidth();
+
break;
+
}
+
 
+
 
+
}
+
 
+
if(maxsize.d_height < area.getHeight() || !i->vfill) {
+
if(i->vfill) { // area too big
+
effective.setHeight(maxsize.d_height);
+
} else { // scaling not requested
+
effective.setHeight(minsize.d_height);
+
}
+
 
+
switch(i->valign) {
+
case AlignTop:
+
offset.d_y = 0.0f;
+
break;
+
case AlignVCenter:
+
offset.d_y = (area.getHeight() - effective.getHeight())/2;
+
break;
+
case AlignBottom:
+
offset.d_y = area.getHeight() - effective.getHeight();
+
break;
+
}
+
 
+
}
+
effective.offset(offset);
+
effective.offset(inner.getPosition());
+
i->child->setArea(absToUni(effective));
+
}
+
 
+
return true;
+
}
+
 
+
void GridLayout::calcMinCellSizes(Scope scope, SizeMap &sz) const {
+
StretchMap &stretch = scope == ScopeCols ? ColStretch_ : RowStretch_;
+
const USizeMap &minsz = scope == ScopeCols ? ColMinSize_ : RowMinSize_;
+
unsigned cnt = scope == ScopeCols ? CellsX_ : CellsY_;
+
 
+
// make our lives easier
+
unsigned Constraints::* pos = scope == ScopeCols ? &Constraints::cx      : &Constraints::cy;
+
unsigned Constraints::* dim = scope == ScopeCols ? &Constraints::cwidth  : &Constraints::cheight;
+
float CEGUI::Size::* szdim  = scope == ScopeCols ? &CEGUI::Size::d_width : &CEGUI::Size::d_height;
+
CEGUI::Size sizeBase(getSizeBase());
+
 
+
sz.clear();
+
for(USizeMap::const_iterator i = minsz.begin(); i != minsz.end(); ++i) {
+
sz[i->first] = i->second.asAbsolute(sizeBase.d_width);
+
}
+
 
+
// at first, only consider the fixed children
+
for(ChildVector::const_iterator i = Children_.begin(); i != Children_.end(); ++i) {
+
if(!i->child->isVisible(true)) continue; // do not reserve space for invisible items
+
if(!isFixedItem(scope, *i)) continue;
+
float margin = i->margin.asAbsolute(sizeBase.d_width);
+
CEGUI::Size childsz = i->child->getMinSize().asAbsolute(sizeBase).asSize();
+
 
+
for(unsigned k = 0; k < (*i).*dim; ++k) {
+
float reqsz = childsz.*szdim / (*i).*dim;
+
 
+
// add margin sizes to the correct rows/cols
+
if(k == 0) reqsz += margin;
+
if(k == (*i).*dim - 1) reqsz += margin;
+
 
+
// update minimum size if applicable
+
if(sz[(*i).*pos + k] < reqsz) sz[(*i).*pos + k] = reqsz;
+
}
+
}
+
 
+
// now set minimum sizes for stretchy columns
+
for(ChildVector::const_iterator i = Children_.begin(); i != Children_.end(); ++i) {
+
if(!i->child->isVisible(true)) continue; // do not reserve space for invisible items
+
unsigned partsum = calcItemStretchSum(scope, *i); // get the base for stretch calculations for this child element
+
if(!partsum) continue; // skip for fixed elements; it would be in vain, because there is nothing left to distribute
+
float margin = i->margin.asAbsolute(sizeBase.d_width);
+
CEGUI::Size childsz = i->child->getMinSize().asAbsolute(sizeBase).asSize();
+
float szneeded = childsz.*szdim + 2*margin;
+
 
+
// first, substract space already distributed for fixed cols/rows
+
for(unsigned k = 0; k < (*i).*dim; ++k) {
+
if(stretch[(*i).*pos + k] == 0) {
+
szneeded -= sz[(*i).*pos + k];
+
}
+
}
+
 
+
// check whether the fixed cols/rows were wide enough already (this time, also consider the margin)
+
if(szneeded <= 0) continue;
+
 
+
// if not, distribute remaining size among them
+
for(unsigned k = 0; k < (*i).*dim; ++k) {
+
float reqsz = szneeded * stretch[(*i).*pos + k] / partsum;
+
 
+
// update minimum size if applicable
+
if(sz[(*i).*pos + k] < reqsz) sz[(*i).*pos + k] = reqsz;
+
}
+
}
+
}
+
 
+
unsigned GridLayout::calcItemStretchSum(Scope scope, const Constraints &item) const {
+
StretchMap &stretch = scope == ScopeCols ? ColStretch_ : RowStretch_;
+
unsigned pos = scope == ScopeCols ? item.cx : item.cy;
+
unsigned dimension = scope == ScopeCols ? item.cwidth : item.cheight;
+
 
+
unsigned sum = 0;
+
  
for(unsigned i = 0; i < dimension; ++i) {
+
== Example ==
sum += stretch[pos+i];
+
The following snippet is an example from the embedded documentation:
}
+
<source lang="cpp">
 +
// create a new window and its layout manager
 +
CEGUI::Window *wnd = (CEGUI::FrameWindow *)CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/FrameWindow", (CEGUI::utf8*)"MyWindow");
 +
GridLayout *layout = new GridLayout(wnd, true); // setting the second parameter to true will
 +
                                                // cause self-destruction of the layout manager on destruction of the window
  
return sum;
+
CEGUI::Window *btn;
}
+
btn = CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/Button", (CEGUI::utf8*)"Btn1"); btn->setText("1");
 +
layout->addChildWindow(btn, 0, 0, 3, 3, UDim(1.0/512, 0.0));
  
inline bool GridLayout::isFixedItem(Scope scope, const Constraints &item) const {
+
btn = CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/Button", (CEGUI::utf8*)"Btn1"); btn->setText("2");
return calcItemStretchSum(scope, item) == 0;
+
btn->setMinSize(CEGUI::UVector2(UDim(0.06, 0.0), UDim(0.03, 0.0)));
}
+
layout->addChildWindow(btn, 2, 0, 1, 1, UDim(1.0/512, 0.0));
  
void GridLayout::calcStretchySizes(Scope scope, float szmax, SizeMap &sz) const {
+
btn = CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/Button", (CEGUI::utf8*)"Btn1"); btn->setText("3");
StretchMap &stretch = scope == ScopeCols ? ColStretch_ : RowStretch_;
+
btn->setMinSize(CEGUI::UVector2(UDim(0.06, 0.0), UDim(0.03, 0.0)));
unsigned cnt = scope == ScopeCols ? CellsX_ : CellsY_;
+
layout->addChildWindow(btn, 2, 1, 1, 1, UDim(1.0/512, 0.0));
float stretchsum = 0;
+
  
// determine remaining size
+
btn = CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/Button", (CEGUI::utf8*)"Btn1"); btn->setText("4");
float szleft = szmax;
+
btn->setMinSize(CEGUI::UVector2(UDim(0.06, 0.0), UDim(0.03, 0.0)));
for(unsigned i = 0; i < cnt; ++i) {
+
layout->addChildWindow(btn, 1, 3, 1, 1, UDim(1.0/512, 0.0));
szleft -= sz[i];
+
stretchsum += stretch[i];
+
}
+
  
// we don't have any stretchable colums/rows
+
btn = CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/Button", (CEGUI::utf8*)"Btn1"); btn->setText("5");
if(stretchsum == 0) return;
+
btn->setMinSize(CEGUI::UVector2(UDim(0.06, 0.0), UDim(0.03, 0.0)));
// distribute remaining size
+
layout->addChildWindow(btn, 2, 3, 1, 1, UDim(1.0/512, 0.0));
for(unsigned i = 0; i < cnt; ++i) {
+
sz[i] += szleft * stretch[i] / stretchsum;
+
}
+
}
+
  
inline CEGUI::Size GridLayout::getSizeBase() const {
+
layout->setRowStretch(0, 0);
const CEGUI::Window *p = LayoutWnd_;
+
layout->setRowStretch(1, 0);
if(CEGUI::System::getSingletonPtr()) {
+
layout->setRowStretch(3, 0);
return CEGUI::System::getSingleton().getRenderer()->getSize();
+
layout->setColStretch(1, 0);
} else {
+
layout->setColStretch(2, 0);
bool cegui_system_must_have_been_initialized=false;
+
assert(cegui_system_must_have_been_initialized);
+
}
+
}
+
  
// ensures default values for unset column/row information
+
layout->setOuterMargin(UDim(1.0/256, 0.0)); // adds a margin to the window's whole client area
void GridLayout::updateData(Scope scope) {
+
wnd->setMinSize(layout->getMinSize()); // asks the layout manager for the window's minimum size
if(scope == ScopeCols) {
+
wnd->setSize(wnd->getMinSize());
for(unsigned i = 0; i < CellsX_; ++i) {
+
layout->layout(); // forces an initial layout
if(ColStretch_.find(i) == ColStretch_.end()) ColStretch_[i] = 1;
+
</source>
if(ColMinSize_.find(i) == ColMinSize_.end()) ColMinSize_[i] = CEGUI::UDim(0.0, 0.0);
+
}
+
} else {
+
for(unsigned i = 0; i < CellsY_; ++i) {
+
if(RowStretch_.find(i) == RowStretch_.end()) RowStretch_[i] = 1;
+
if(RowMinSize_.find(i) == RowMinSize_.end()) RowMinSize_[i] = CEGUI::UDim(0.0, 0.0);
+
}
+
}
+
}
+
  
void GridLayout::removeChildWindow_impl(ChildVector::iterator i) {
+
The resulting window will look like this:
if(i->child->getParent() == LayoutWnd_) {
+
{| style="width:300px; height:200px; border: 1px solid black;text-align: center"
LayoutWnd_->removeChildWindow(i->child);
+
| colspan="2" rowspan="3" style="border: 1px solid black;text-align: center" | 1
}
+
| style="border: 1px solid black;width: 70px;height: 1.5em" | 2
 +
|-
 +
| style="border: 1px solid black;width: 70px;height: 1.5em" | 3
 +
|-
 +
|
 +
|-
 +
|
 +
| style="border: 1px solid black;width: 70px;height: 1.5em" | 4
 +
| style="border: 1px solid black;width: 70px;height: 1.5em" | 5
 +
|}
 +
Only the button captioned "1" will resize when resizing the window and when viewing the window with a different resolution, everything will look the same as before
 +
== Download ==
 +
The files are too big to be inlined into this page. You may download them here
 +
* [http://noware.info/progs/cegui/gridlayout/GridLayout.h GridLayout.h]
 +
* [http://noware.info/progs/cegui/gridlayout/GridLayout.cpp GridLayout.cpp]
  
Children_.erase(i);
+
[[Category:HowTo]]
}
+
</code>
+

Latest revision as of 23:22, 3 March 2011

Written for CEGUI 0.6


Works with versions 0.6.x (obsolete)

Introduction

It is hard to hard-code a layout that is constantly looking good with different screen resolutions and for different window sizes - especially when dealing with resizable windows. Often, two wdgets are too close together or too far apart. The GridLayout class solves such problems: This class provides a layout manager for CEGUI that lays out a given window according to an invisible grid laid over the window's client area. The size of the grid's cells is changed according to some parameters when the window size changes. The widgets, the client application registers with the layout manager, stick to specific cells and change their size when the cells do. This concept (inspired by Trolltechs Qt) allows for many different and complex window layouts. The header file is documented with doxygen tags.

Questions, remarks, comments, blame, etc. shall be posted within the GridLayout forum thread.

Example

The following snippet is an example from the embedded documentation:

// create a new window and its layout manager
CEGUI::Window *wnd = (CEGUI::FrameWindow *)CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/FrameWindow", (CEGUI::utf8*)"MyWindow");
GridLayout *layout = new GridLayout(wnd, true); // setting the second parameter to true will
                                                // cause self-destruction of the layout manager on destruction of the window
 
CEGUI::Window *btn;
btn = CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/Button", (CEGUI::utf8*)"Btn1"); btn->setText("1");
layout->addChildWindow(btn, 0, 0, 3, 3, UDim(1.0/512, 0.0));
 
btn = CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/Button", (CEGUI::utf8*)"Btn1"); btn->setText("2");
btn->setMinSize(CEGUI::UVector2(UDim(0.06, 0.0), UDim(0.03, 0.0)));
layout->addChildWindow(btn, 2, 0, 1, 1, UDim(1.0/512, 0.0));
 
btn = CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/Button", (CEGUI::utf8*)"Btn1"); btn->setText("3");
btn->setMinSize(CEGUI::UVector2(UDim(0.06, 0.0), UDim(0.03, 0.0)));
layout->addChildWindow(btn, 2, 1, 1, 1, UDim(1.0/512, 0.0));
 
btn = CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/Button", (CEGUI::utf8*)"Btn1"); btn->setText("4");
btn->setMinSize(CEGUI::UVector2(UDim(0.06, 0.0), UDim(0.03, 0.0)));
layout->addChildWindow(btn, 1, 3, 1, 1, UDim(1.0/512, 0.0));
 
btn = CEGUI::WindowManager::getSingleton().createWindow("TaharezLook/Button", (CEGUI::utf8*)"Btn1"); btn->setText("5");
btn->setMinSize(CEGUI::UVector2(UDim(0.06, 0.0), UDim(0.03, 0.0)));
layout->addChildWindow(btn, 2, 3, 1, 1, UDim(1.0/512, 0.0));
 
layout->setRowStretch(0, 0);
layout->setRowStretch(1, 0);
layout->setRowStretch(3, 0);
layout->setColStretch(1, 0);
layout->setColStretch(2, 0);
 
layout->setOuterMargin(UDim(1.0/256, 0.0)); // adds a margin to the window's whole client area
wnd->setMinSize(layout->getMinSize()); // asks the layout manager for the window's minimum size
wnd->setSize(wnd->getMinSize());
layout->layout(); // forces an initial layout

The resulting window will look like this:

1 2
3
4 5

Only the button captioned "1" will resize when resizing the window and when viewing the window with a different resolution, everything will look the same as before

Download

The files are too big to be inlined into this page. You may download them here