stk-code_catmod/src/guiengine/layout_manager.cpp
auria b71a93feb9 Update layout manager to no more crash when the screen is full (which happened with Arabic, for some reason there is much empty space above and below arabic characters)
git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/main/trunk@11124 178a84e3-b1eb-0310-8ba1-8eac791a3b58
2012-04-18 20:49:34 +00:00

701 lines
26 KiB
C++

// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2010 Marianne Gagnon
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "guiengine/layout_manager.hpp"
#include <iostream>
#include <IGUIFont.h>
#include <ITexture.h>
using namespace irr;
using namespace gui;
using namespace video;
#include "graphics/irr_driver.hpp"
#include "guiengine/abstract_top_level_container.hpp"
#include "guiengine/engine.hpp"
#include "guiengine/scalable_font.hpp"
#include "guiengine/widget.hpp"
#include "io/file_manager.hpp"
#include "utils/ptr_vector.hpp"
#include "utils/string_utils.hpp"
using namespace GUIEngine;
#ifndef round
# define round(x) (floor(x+0.5f))
#endif
/** Like atoi, but on error prints an error message to stderr */
int atoi_p(const char* val)
{
int i;
if (StringUtils::parseString<int>(val, &i))
{
return i;
}
else
{
fprintf(stderr, "[LayoutManager] WARNING: Invalid value '%s' found in XML file where integer was expected\n", val);
return 0;
}
}
// ----------------------------------------------------------------------------
bool LayoutManager::convertToCoord(std::string& x, int* absolute /* out */, int* percentage /* out */)
{
bool is_number;
int i;
std::istringstream myStream(x);
is_number = (myStream >> i)!=0;
if(!is_number) return false;
if( x[x.size()-1] == '%' ) // percentage
{
*percentage = i;
return true;
}
else // absolute number
{
*absolute = i;
return true;
}
}
// ----------------------------------------------------------------------------
void LayoutManager::readCoords(Widget* self)
{
// determine widget position and size if not already done by sizers
std::string x = self->m_properties[PROP_X];
std::string y = self->m_properties[PROP_Y];
std::string width = self->m_properties[PROP_WIDTH];
std::string height = self->m_properties[PROP_HEIGHT];
/*
// retrieve parent size (or screen size if none). Will be useful for layout
// and especially for percentages.
unsigned int parent_w, parent_h, parent_x, parent_y;
if(parent == NULL)
{
//core::dimension2d<u32> frame_size = GUIEngine::getDriver()->getCurrentRenderTargetSize();
//parent_w = frame_size.Width;
//parent_h = frame_size.Height;
parent_w = topLevelContainer->getWidth();
parent_h = topLevelContainer->getHeight();
parent_x = 0;
parent_y = 0;
}
else
{
parent_w = parent->m_w;
parent_h = parent->m_h;
parent_x = parent->m_x;
parent_y = parent->m_y;
}
*/
// ---- try converting to number
// x coord
{
int abs_x = 0x7FFFFFFF, percent_x = 0x7FFFFFFF;
if (convertToCoord(x, &abs_x, &percent_x ))
{
if (abs_x >= 0 && abs_x != 0x7FFFFFFF)
{
self->m_absolute_x = abs_x;
}
else if (abs_x < 0)
{
self->m_absolute_reverse_x = abs(abs_x);
}
else if (percent_x >= 0 && percent_x != 0x7FFFFFFF)
{
self->m_relative_x = (float)percent_x;
}
}
}
// y coord
{
int abs_y = 0x7FFFFFFF, percent_y = 0x7FFFFFFF;
if (convertToCoord(y, &abs_y, &percent_y ))
{
if (abs_y >= 0 && abs_y != 0x7FFFFFFF)
{
self->m_absolute_y = abs_y;
}
else if (abs_y < 0)
{
self->m_absolute_reverse_y = abs(abs_y);
}
else if (percent_y >= 0 && percent_y != 0x7FFFFFFF)
{
self->m_relative_y = (float)percent_y;
}
}
}
// ---- if this widget has an icon, get icon size. this can helpful determine its optimal size
int texture_w = -1, texture_h = -1;
if (self->m_properties[PROP_ICON].size() > 0)
{
ITexture* texture = irr_driver->getTexture((file_manager->getDataDir() + "/" +
self->m_properties[PROP_ICON]).c_str());
if (texture != NULL)
{
texture_w = texture->getSize().Width;
texture_h = texture->getSize().Height;
}
}
// ---- if this widget has a label, get text size. this can helpful determine its optimal size
int label_w = -1, label_h = -1;
if (self->m_text.size() > 0)
{
IGUIFont* font = (self->m_title_font ? GUIEngine::getTitleFont() : GUIEngine::getFont());
core::dimension2d< u32 > dim = font->getDimension( self->m_text.c_str() );
label_w = dim.Width + self->getWidthNeededAroundLabel();
// FIXME - won't work with multiline labels. thus, for now, when multiple
// lines are required, we need to specify a height explicitely
label_h = dim.Height + self->getHeightNeededAroundLabel();
}
else if (self->getType() == WTYPE_CHECKBOX)
{
// User text height to guess checkbox size
IGUIFont* font = (self->m_title_font ? GUIEngine::getTitleFont() : GUIEngine::getFont());
core::dimension2d< u32 > dim = font->getDimension( L"X" );
label_h = dim.Height + self->getHeightNeededAroundLabel();
label_w = label_h; // a checkbox is square
}
if (label_h == -1)
{
if (self->getType() == WTYPE_TEXTBOX ||
self->getType() == WTYPE_BUTTON ||
self->getType() == WTYPE_LABEL ||
self->getType() == WTYPE_SPINNER)
{
IGUIFont* font = (self->m_title_font ? GUIEngine::getTitleFont() : GUIEngine::getFont());
// get text height, a text box, button or label is always as high as the text it could contain
core::dimension2d< u32 > dim = font->getDimension( L"X" );
label_h = dim.Height + self->getHeightNeededAroundLabel();
}
}
// ---- if this widget has children, get their size size. this can helpful determine its optimal size
if (self->m_properties[PROP_WIDTH] == "fit" || self->m_properties[PROP_HEIGHT] == "fit")
{
bool is_vertical_row = (self->m_properties[PROP_LAYOUT] == "vertical-row");
bool is_horizontal_row = (self->m_properties[PROP_LAYOUT] == "horizontal-row");
int child_max_width = -1, child_max_height = -1;
int total_width = 0, total_height = 0;
for (int child=0; child<self->m_children.size(); child++)
{
if (self->m_children[child].m_absolute_w > -1)
{
if (is_horizontal_row)
{
total_width += self->m_children[child].m_absolute_w ;
}
else
{
if (child_max_width == -1 || self->m_children[child].m_absolute_w > child_max_width)
{
child_max_width = self->m_children[child].m_absolute_w;
}
}
}
if (self->m_children[child].m_absolute_h > -1)
{
if (is_vertical_row)
{
total_height += self->m_children[child].m_absolute_h;
}
else
{
if (child_max_height == -1 || self->m_children[child].m_absolute_h > child_max_height)
{
child_max_height = self->m_children[child].m_absolute_h;
}
}
}
}
if (self->m_properties[PROP_WIDTH] == "fit")
{
self->m_absolute_w = (is_horizontal_row ? total_width : child_max_width);
}
if (self->m_properties[PROP_HEIGHT] == "fit")
{
self->m_absolute_h = (is_vertical_row ? total_height : child_max_height);
}
}
// ---- read dimension
// width
{
int abs_w = -1, percent_w = -1;
if (width == "font") self->m_absolute_w = GUIEngine::getFontHeight();
else if (convertToCoord(width, &abs_w, &percent_w ))
{
if (abs_w > -1) self->m_absolute_w = abs_w;
else if (percent_w > -1) self->m_relative_w = (float)percent_w;
}
else if(texture_w > -1) self->m_absolute_w = texture_w;
else if(label_w > -1) self->m_absolute_w = label_w;
}
// height
{
int abs_h = -1, percent_h = -1;
if (height == "font") self->m_absolute_h = GUIEngine::getFontHeight();
else if (convertToCoord(height, &abs_h, &percent_h ))
{
if (abs_h > -1) self->m_absolute_h = abs_h;
else if (percent_h > -1) self->m_relative_h = (float)percent_h;
}
else if (texture_h > -1 && label_h > -1) self->m_absolute_h = texture_h + label_h; // label + icon
else if (texture_h > -1) self->m_absolute_h = texture_h;
else if (label_h > -1) self->m_absolute_h = label_h;
}
}
// ----------------------------------------------------------------------------
void LayoutManager::applyCoords(Widget* self, AbstractTopLevelContainer* topLevelContainer, Widget* parent)
{
// retrieve parent size (or screen size if none). Will be useful for layout
// and especially for percentages.
unsigned int parent_w, parent_h, parent_x, parent_y;
if(parent == NULL)
{
//core::dimension2d<u32> frame_size = GUIEngine::getDriver()->getCurrentRenderTargetSize();
//parent_w = frame_size.Width;
//parent_h = frame_size.Height;
parent_w = topLevelContainer->getWidth();
parent_h = topLevelContainer->getHeight();
parent_x = 0;
parent_y = 0;
}
else
{
parent_w = parent->m_w;
parent_h = parent->m_h;
parent_x = parent->m_x;
parent_y = parent->m_y;
}
if (self->m_absolute_x > -1) self->m_x = parent_x + self->m_absolute_x;
else if (self->m_absolute_reverse_x > -1) self->m_x = parent_x + (parent_w - self->m_absolute_reverse_x);
else if (self->m_relative_x > -1) self->m_x = (int)(parent_x + parent_w*self->m_relative_x/100);
if (self->m_absolute_y > -1) self->m_y = parent_y + self->m_absolute_y;
else if (self->m_absolute_reverse_y > -1) self->m_y = parent_y + (parent_h - self->m_absolute_reverse_y);
else if (self->m_relative_y > -1) self->m_y = (int)(parent_y + parent_h*self->m_relative_y/100);
if (self->m_absolute_w > -1) self->m_w = self->m_absolute_w;
else if (self->m_relative_w > -1) self->m_w = (int)round(parent_w*self->m_relative_w/100.0);
if (self->m_absolute_h > -1) self->m_h = self->m_absolute_h;
else if (self->m_relative_h > -1) self->m_h = (int)round(parent_h*self->m_relative_h/100.0);
// ---- can't make widget bigger than parent
if (self->m_h > (int)parent_h)
{
float ratio = (float)parent_h / self->m_h;
self->m_w = (int)(self->m_w*ratio);
self->m_h = (int)(self->m_h*ratio);
}
if (self->m_w > (int)parent_w)
{
// scale down while keeping aspect ratio (don't do this for text widgets though...)
float ratio = (float)parent_w / self->m_w;
self->m_w = (int)(self->m_w * ratio);
// FIXME: ugly to hardcode widgets types here
if (self->m_type != WTYPE_LABEL && self->m_type != WTYPE_BUBBLE)
{
self->m_h = (int)(self->m_h * ratio);
}
}
// ------ check for given max size
if (self->m_properties[PROP_MAX_WIDTH].size() > 0)
{
const int max_width = atoi_p( self->m_properties[PROP_MAX_WIDTH].c_str() );
if (self->m_w > max_width) self->m_w = max_width;
}
if (self->m_properties[PROP_MAX_HEIGHT].size() > 0)
{
const int max_height = atoi_p( self->m_properties[PROP_MAX_HEIGHT].c_str() );
if (self->m_h > max_height) self->m_h = max_height;
}
}
// ----------------------------------------------------------------------------
void LayoutManager::recursivelyReadCoords(PtrVector<Widget>& widgets)
{
const unsigned short widgets_amount = widgets.size();
// ----- deal with containers' children
for (int n=0; n<widgets_amount; n++)
{
if (widgets[n].m_type == WTYPE_DIV) recursivelyReadCoords(widgets[n].m_children);
}
// ----- read x/y/size parameters
for (unsigned short n=0; n<widgets_amount; n++)
{
readCoords(widgets.get(n));
}//next widget
}
// ----------------------------------------------------------------------------
void LayoutManager::calculateLayout(PtrVector<Widget>& widgets, AbstractTopLevelContainer* topLevelContainer)
{
recursivelyReadCoords(widgets);
doCalculateLayout(widgets, topLevelContainer, NULL);
}
// ----------------------------------------------------------------------------
void LayoutManager::doCalculateLayout(PtrVector<Widget>& widgets, AbstractTopLevelContainer* topLevelContainer,
Widget* parent)
{
const unsigned short widgets_amount = widgets.size();
for (int n=0; n<widgets_amount; n++)
{
applyCoords(widgets.get(n), topLevelContainer, parent);
}
// ----- manage 'layout's if relevant
do // i'm using 'while false' here just to be able to 'break' ...
{
if(parent == NULL) break;
std::string layout_name = parent->m_properties[PROP_LAYOUT];
if(layout_name.size() < 1) break;
bool horizontal = false;
if (!strcmp("horizontal-row", layout_name.c_str()))
horizontal = true;
else if(!strcmp("vertical-row", layout_name.c_str()))
horizontal = false;
else
{
std::cerr << "Unknown layout name : " << layout_name.c_str() << std::endl;
break;
}
const int w = parent->m_w, h = parent->m_h;
// find space left after placing all absolutely-sized widgets in a row
// (the space left will be divided between remaining widgets later)
int left_space = (horizontal ? w : h);
unsigned short total_proportion = 0;
for(int n=0; n<widgets_amount; n++)
{
// relatively-sized widget
std::string prop = widgets[n].m_properties[ PROP_PROPORTION ];
if(prop.size() != 0)
{
total_proportion += atoi_p( prop.c_str() );
continue;
}
// absolutely-sized widgets
left_space -= (horizontal ? widgets[n].m_w : widgets[n].m_h);
} // next widget
if (left_space < 0)
{
fprintf(stderr, "[LayoutManager] WARNING: statically sized widgets took all the place!!\n");
left_space = 0;
}
// ---- lay widgets in row
int x = parent->m_x;
int y = parent->m_y;
for (int n=0; n<widgets_amount; n++)
{
std::string prop = widgets[n].m_properties[ PROP_PROPORTION ];
if(prop.size() != 0)
{
// proportional size
int proportion = 1;
std::istringstream myStream(prop);
if (!(myStream >> proportion))
{
std::cerr << "/!\\ Warning /!\\ : proportion '" << prop.c_str()
<< "' is not a number for widget " << widgets[n].m_properties[PROP_ID].c_str()
<< std::endl;
}
const float fraction = (float)proportion/(float)total_proportion;
if (horizontal)
{
widgets[n].m_x = x;
std::string align = widgets[n].m_properties[ PROP_ALIGN ];
if (align.size() < 1)
{
std::string prop_y = widgets[n].m_properties[ PROP_Y ];
if (prop_y.size() > 0)
{
if (prop_y[ prop_y.size()-1 ] == '%')
{
prop_y = prop_y.substr(0, prop_y.size() - 1);
widgets[n].m_y = (int)(y + atoi_p(prop_y.c_str())/100.0f * h);
}
else
{
widgets[n].m_y = y + atoi_p(prop_y.c_str());
}
}
else
{
widgets[n].m_y = y;
}
}
else if (align == "top")
{
widgets[n].m_y = y;
}
else if (align == "center")
{
widgets[n].m_y = y + h/2 - widgets[n].m_h/2;
}
else if (align == "bottom")
{
widgets[n].m_y = y + h - widgets[n].m_h;
}
else
{
std::cerr << "/!\\ Warning /!\\ : alignment '" << align.c_str()
<< "' is unknown (widget '" << widgets[n].m_properties[PROP_ID].c_str()
<< "', in a horiozntal-row layout)\n";
}
widgets[n].m_w = (int)(left_space*fraction);
if (widgets[n].m_properties[PROP_MAX_WIDTH].size() > 0)
{
const int max_width = atoi_p( widgets[n].m_properties[PROP_MAX_WIDTH].c_str() );
if (widgets[n].m_w > max_width) widgets[n].m_w = max_width;
}
if (widgets[n].m_w <= 0)
{
fprintf(stderr, "WARNING: widget '%s' has a width of %i (left_space = %i, "
"fraction = %f, max_width = %s)\n", widgets[n].m_properties[PROP_ID].c_str(),
widgets[n].m_w, left_space, fraction, widgets[n].m_properties[PROP_MAX_WIDTH].c_str());
widgets[n].m_w = 1;
}
x += widgets[n].m_w;
}
else
{
widgets[n].m_h = (int)(left_space*fraction);
if (widgets[n].m_properties[PROP_MAX_HEIGHT].size() > 0)
{
const int max_height = atoi_p( widgets[n].m_properties[PROP_MAX_HEIGHT].c_str() );
if (widgets[n].m_h > max_height) widgets[n].m_h = max_height;
}
if (widgets[n].m_h <= 0)
{
fprintf(stderr, "WARNING: widget '%s' has a height of %i (left_space = %i, "
"fraction = %f, max_width = %s)\n", widgets[n].m_properties[PROP_ID].c_str(),
widgets[n].m_h, left_space, fraction, widgets[n].m_properties[PROP_MAX_WIDTH].c_str());
widgets[n].m_h = 1;
}
std::string align = widgets[n].m_properties[ PROP_ALIGN ];
if (align.size() < 1)
{
std::string prop_x = widgets[n].m_properties[ PROP_X ];
if (prop_x.size() > 0)
{
if (prop_x[ prop_x.size()-1 ] == '%')
{
prop_x = prop_x.substr(0, prop_x.size() - 1);
widgets[n].m_x = (int)(x + atoi_p(prop_x.c_str())/100.0f * w);
}
else
{
widgets[n].m_x = x + atoi_p(prop_x.c_str());
}
}
else
{
widgets[n].m_x = x;
}
}
else if (align == "left")
{
widgets[n].m_x = x;
}
else if (align == "center")
{
widgets[n].m_x = x + w/2 - widgets[n].m_w/2;
}
else if (align == "right")
{
widgets[n].m_x = x + w - widgets[n].m_w;
}
else
{
std::cerr << "/!\\ Warning /!\\ : alignment '" << align.c_str()
<< "' is unknown (widget '" << widgets[n].m_properties[PROP_ID].c_str()
<< "', in a vertical-row layout)\n";
}
widgets[n].m_y = y;
y += widgets[n].m_h;
}
}
else
{
// absolute size
if (horizontal)
{
widgets[n].m_x = x;
std::string align = widgets[n].m_properties[ PROP_ALIGN ];
if (align.size() < 1)
{
std::string prop_y = widgets[n].m_properties[ PROP_Y ];
if (prop_y.size() > 0)
{
if (prop_y[ prop_y.size()-1 ] == '%')
{
prop_y = prop_y.substr(0, prop_y.size() - 1);
widgets[n].m_y = (int)(y + atoi_p(prop_y.c_str())/100.0f * h);
}
else
{
widgets[n].m_y = y + atoi_p(prop_y.c_str());
}
}
else
{
widgets[n].m_y = y;
}
}
else if (align == "top")
{
widgets[n].m_y = y;
}
else if (align == "center")
{
widgets[n].m_y = y + h/2 - widgets[n].m_h/2;
}
else if (align == "bottom")
{
widgets[n].m_y = y + h - widgets[n].m_h;
}
else
{
std::cerr << "/!\\ Warning /!\\ : alignment '" << align.c_str()
<< "' is unknown in widget " << widgets[n].m_properties[PROP_ID].c_str()
<< std::endl;
}
x += widgets[n].m_w;
}
else
{
std::string align = widgets[n].m_properties[ PROP_ALIGN ];
if (align.size() < 1)
{
std::string prop_x = widgets[n].m_properties[ PROP_X ];
if (prop_x.size() > 0)
{
if (prop_x[ prop_x.size()-1 ] == '%')
{
prop_x = prop_x.substr(0, prop_x.size() - 1);
widgets[n].m_x = (int)(x + atoi_p(prop_x.c_str())/100.0f * w);
}
else
{
widgets[n].m_x = x + atoi_p(prop_x.c_str());
}
}
else
{
widgets[n].m_x = x;
}
}
else if (align == "left")
{
widgets[n].m_x = x;
}
else if (align == "center")
{
widgets[n].m_x = x + w/2 - widgets[n].m_w/2;
}
else if (align == "right")
{
widgets[n].m_x = x + w - widgets[n].m_w;
}
else
{
std::cerr << "/!\\ Warning /!\\ : alignment '" << align.c_str()
<< "' is unknown in widget " << widgets[n].m_properties[PROP_ID].c_str() << std::endl;
}
widgets[n].m_y = y;
y += widgets[n].m_h;
}
} // end if property or absolute size
} // next widget
} while(false);
// ----- also deal with containers' children
for (int n=0; n<widgets_amount; n++)
{
if (widgets[n].m_type == WTYPE_DIV) doCalculateLayout(widgets[n].m_children, topLevelContainer, &widgets[n]);
}
} // calculateLayout