GUI layout management and a few visual/eye-candy modifications. See
DOCS/README.layout in the base package for details, along with the modified dialog files.
This commit is contained in:
parent
944a82b576
commit
eeeee3a53e
7 changed files with 620 additions and 10 deletions
|
@ -1,4 +1,5 @@
|
|||
noinst_LIBRARIES = libGUI.a
|
||||
noinst_PROGRAMS = layout-test
|
||||
|
||||
libGUI_a_SOURCES = \
|
||||
new_gui.cxx new_gui.hxx \
|
||||
|
@ -12,6 +13,13 @@ libGUI_a_SOURCES = \
|
|||
sgVec3Slider.cxx sgVec3Slider.hxx \
|
||||
trackball.c trackball.h \
|
||||
puList.cxx puList.hxx \
|
||||
AirportList.cxx AirportList.hxx
|
||||
AirportList.cxx AirportList.hxx \
|
||||
layout.cxx layout-props.cxx layout.hxx
|
||||
|
||||
INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/src
|
||||
|
||||
layout_test_SOURCES = layout-test.cxx
|
||||
|
||||
layout_test_LDADD = libGUI.a \
|
||||
-lsgprops -lsgdebug -lsgstructure -lsgmisc -lsgxml \
|
||||
-lplibpw -lplibpu -lplibfnt -lplibul $(opengl_LIBS)
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include "puList.hxx"
|
||||
#include "AirportList.hxx"
|
||||
#include "layout.hxx"
|
||||
|
||||
int fgPopup::checkHit(int button, int updown, int x, int y)
|
||||
{
|
||||
|
@ -235,9 +236,20 @@ FGDialog::display (SGPropertyNode * props)
|
|||
return;
|
||||
}
|
||||
|
||||
_object = makeObject(props,
|
||||
globals->get_props()->getIntValue("/sim/startup/xsize"),
|
||||
globals->get_props()->getIntValue("/sim/startup/ysize"));
|
||||
int screenw = globals->get_props()->getIntValue("/sim/startup/xsize");
|
||||
int screenh = globals->get_props()->getIntValue("/sim/startup/ysize");
|
||||
|
||||
LayoutWidget wid(props);
|
||||
int pw=0, ph=0;
|
||||
if(!props->hasValue("width") || !props->hasValue("height"))
|
||||
wid.calcPrefSize(&pw, &ph);
|
||||
pw = props->getIntValue("width", pw);
|
||||
ph = props->getIntValue("height", ph);
|
||||
int px = props->getIntValue("x", (screenw - pw) / 2);
|
||||
int py = props->getIntValue("y", (screenh - ph) / 2);
|
||||
wid.layout(px, py, pw, ph);
|
||||
|
||||
_object = makeObject(props, screenw, screenh);
|
||||
|
||||
if (_object != 0) {
|
||||
_object->reveal();
|
||||
|
@ -251,9 +263,9 @@ FGDialog::display (SGPropertyNode * props)
|
|||
puObject *
|
||||
FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
|
||||
{
|
||||
bool presetSize = props->hasValue("width") && props->hasValue("height");
|
||||
int width = props->getIntValue("width", parentWidth);
|
||||
int height = props->getIntValue("height", parentHeight);
|
||||
|
||||
int x = props->getIntValue("x", (parentWidth - width) / 2);
|
||||
int y = props->getIntValue("y", (parentHeight - height) / 2);
|
||||
|
||||
|
@ -288,10 +300,21 @@ FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
|
|||
} else if (type == "text") {
|
||||
puText * text = new puText(x, y);
|
||||
setupObject(text, props);
|
||||
// Layed-out objects need their size set, and non-layout ones
|
||||
// get a different placement.
|
||||
if(presetSize) text->setSize(width, height);
|
||||
else text->setLabelPlace(PUPLACE_LABEL_DEFAULT);
|
||||
return text;
|
||||
} else if (type == "checkbox") {
|
||||
puButton * b;
|
||||
b = new puButton(x, y, x + width, y + height, PUBUTTON_XCHECK);
|
||||
b->setColourScheme(.8, .7, .7); // matches "PUI input pink"
|
||||
setupObject(b, props);
|
||||
return b;
|
||||
} else if (type == "radio") {
|
||||
puButton * b;
|
||||
b = new puButton(x, y, x + width, y + height, PUBUTTON_CIRCLE);
|
||||
b->setColourScheme(.8, .7, .7); // matches "PUI input pink"
|
||||
setupObject(b, props);
|
||||
return b;
|
||||
} else if (type == "button") {
|
||||
|
@ -301,6 +324,8 @@ FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
|
|||
b = new puOneShot(x, y, legend);
|
||||
else
|
||||
b = new puButton(x, y, legend);
|
||||
if(presetSize)
|
||||
b->setSize(width, height);
|
||||
setupObject(b, props);
|
||||
return b;
|
||||
} else if (type == "combo") {
|
||||
|
@ -354,6 +379,8 @@ FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
|
|||
void
|
||||
FGDialog::setupObject (puObject * object, SGPropertyNode * props)
|
||||
{
|
||||
object->setLabelPlace(PUPLACE_CENTERED_RIGHT);
|
||||
|
||||
if (props->hasValue("legend"))
|
||||
object->setLegend(props->getStringValue("legend"));
|
||||
|
||||
|
@ -391,8 +418,10 @@ FGDialog::setupGroup (puGroup * group, SGPropertyNode * props,
|
|||
{
|
||||
setupObject(group, props);
|
||||
|
||||
if (makeFrame)
|
||||
new puFrame(0, 0, width, height);
|
||||
if (makeFrame) {
|
||||
puFrame* f = new puFrame(0, 0, width, height);
|
||||
f->setColorScheme(0.8, 0.8, 0.9, 0.85);
|
||||
}
|
||||
|
||||
int nChildren = props->nChildren();
|
||||
for (int i = 0; i < nChildren; i++)
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
#include "gui.h"
|
||||
#include "gui_local.hxx"
|
||||
#include "preset_dlg.hxx"
|
||||
|
||||
#include "layout.hxx"
|
||||
|
||||
extern void initDialog (void);
|
||||
extern void mkDialogInit (void);
|
||||
|
@ -71,8 +71,8 @@ void guiInit()
|
|||
|
||||
// Initialize PUI
|
||||
puInit();
|
||||
puSetDefaultStyle ( PUSTYLE_SMALL_BEVELLED ); //PUSTYLE_DEFAULT
|
||||
puSetDefaultColourScheme (0.8, 0.8, 0.9, 0.8);
|
||||
puSetDefaultStyle ( PUSTYLE_SMALL_SHADED ); //PUSTYLE_DEFAULT
|
||||
puSetDefaultColourScheme (0.8, 0.8, 0.9, 1);
|
||||
|
||||
initDialog();
|
||||
|
||||
|
@ -94,6 +94,8 @@ void guiInit()
|
|||
puFont GuiFont ( guiFntHandle, 15 ) ;
|
||||
puSetDefaultFonts( GuiFont, GuiFont ) ;
|
||||
guiFnt = puGetDefaultLabelFont();
|
||||
|
||||
LayoutWidget::setDefaultFont(&GuiFont, 15);
|
||||
|
||||
if (!fgHasNode("/sim/startup/mouse-pointer")) {
|
||||
// no preference specified for mouse pointer, attempt to autodetect...
|
||||
|
|
88
src/GUI/layout-props.cxx
Normal file
88
src/GUI/layout-props.cxx
Normal file
|
@ -0,0 +1,88 @@
|
|||
#include <plib/pu.h>
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
#include "layout.hxx"
|
||||
|
||||
// This file contains the code implementing the LayoutWidget class in
|
||||
// terms of a PropertyNode (plus a tiny bit of PUI glue). See
|
||||
// layout.cxx for the actual layout engine.
|
||||
|
||||
puFont LayoutWidget::FONT;
|
||||
|
||||
void LayoutWidget::setDefaultFont(puFont* font, int pixels)
|
||||
{
|
||||
UNIT = (int)(pixels * (1/3.) + 0.999);
|
||||
FONT = *font;
|
||||
}
|
||||
|
||||
int LayoutWidget::stringLength(const char* s)
|
||||
{
|
||||
return (int)(FONT.getFloatStringWidth(s) + 0.999);
|
||||
}
|
||||
|
||||
const char* LayoutWidget::type()
|
||||
{
|
||||
const char* t = _prop->getName();
|
||||
return (*t == 0) ? "dialog" : t;
|
||||
}
|
||||
|
||||
bool LayoutWidget::hasParent()
|
||||
{
|
||||
return _prop->getParent() ? true : false;
|
||||
}
|
||||
|
||||
LayoutWidget LayoutWidget::parent()
|
||||
{
|
||||
return LayoutWidget(_prop->getParent());
|
||||
}
|
||||
|
||||
int LayoutWidget::nChildren()
|
||||
{
|
||||
// Hack: assume that any non-leaf nodes are widgets...
|
||||
int n = 0;
|
||||
for(int i=0; i<_prop->nChildren(); i++)
|
||||
if(_prop->getChild(i)->nChildren() != 0)
|
||||
n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
LayoutWidget LayoutWidget::getChild(int idx)
|
||||
{
|
||||
// Same hack. Note that access is linear time in the number of
|
||||
// children...
|
||||
int n = 0;
|
||||
for(int i=0; i<_prop->nChildren(); i++) {
|
||||
SGPropertyNode* p = _prop->getChild(i);
|
||||
if(p->nChildren() != 0) {
|
||||
if(idx == n) return LayoutWidget(p);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
return LayoutWidget(0);
|
||||
}
|
||||
|
||||
bool LayoutWidget::hasField(const char* f)
|
||||
{
|
||||
return _prop->hasChild(f);
|
||||
}
|
||||
|
||||
int LayoutWidget::getNum(const char* f)
|
||||
{
|
||||
return _prop->getIntValue(f);
|
||||
}
|
||||
|
||||
bool LayoutWidget::getBool(const char* f)
|
||||
{
|
||||
return _prop->getBoolValue(f);
|
||||
}
|
||||
|
||||
const char* LayoutWidget::getStr(const char* f)
|
||||
{
|
||||
return _prop->getStringValue(f);
|
||||
}
|
||||
|
||||
void LayoutWidget::setNum(const char* f, int num)
|
||||
{
|
||||
_prop->setIntValue(f, num);
|
||||
}
|
||||
|
56
src/GUI/layout-test.cxx
Normal file
56
src/GUI/layout-test.cxx
Normal file
|
@ -0,0 +1,56 @@
|
|||
#include <iostream>
|
||||
|
||||
#include <GL/gl.h>
|
||||
#include <plib/pw.h>
|
||||
#include <plib/pu.h>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
|
||||
#include "layout.hxx"
|
||||
|
||||
// Takes a property file on the command line, lays it out, and writes
|
||||
// the resulting tree back to stdout. Requires that the
|
||||
// "Helvetica.txf" font file from the base package be in the current
|
||||
// directory.
|
||||
|
||||
// g++ -Wall -g -o layout layout.cxx layout-props.cxx layout-test.cxx
|
||||
// -I/fg/include -L/fg/lib -I.. -lsgprops -lsgdebug -lsgstructure
|
||||
// -lsgmisc -lsgxml -lplibpw -lplibpu -lplibfnt -lplibul -lGL
|
||||
|
||||
// We can't load a plib fntTexFont without a GL context, so we use the
|
||||
// PW library to initialize things. The callbacks are required, but
|
||||
// just stubs.
|
||||
void exitCB(){ pwCleanup(); exit(0); }
|
||||
void resizeCB(int w, int h){ }
|
||||
void mouseMotionCB(int x, int y){ puMouse(x, y); }
|
||||
void mouseButtonCB(int button, int updn, int x, int y){ puMouse(button, updn, x, y); }
|
||||
void keyboardCB(int key, int updn, int x, int y){ puKeyboard(key, updn, x, y); }
|
||||
|
||||
const char* FONT_FILE = "Helvetica.txf";
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
FILE* tmp;
|
||||
if(!(tmp = fopen(FONT_FILE, "r"))) {
|
||||
fprintf(stderr, "Could not open %s for reading.\n", FONT_FILE);
|
||||
exit(1);
|
||||
}
|
||||
fclose(tmp);
|
||||
|
||||
pwInit(0, 0, 600, 400, 0, "Layout Test", true, 0);
|
||||
pwSetCallbacks(keyboardCB, mouseButtonCB, mouseMotionCB,
|
||||
resizeCB, exitCB);
|
||||
|
||||
fntTexFont helv;
|
||||
helv.load(FONT_FILE);
|
||||
puFont puhelv(&helv);
|
||||
|
||||
LayoutWidget::setDefaultFont(&puhelv, 15);
|
||||
SGPropertyNode props;
|
||||
readProperties(argv[1], &props);
|
||||
LayoutWidget w(&props);
|
||||
int pw=0, ph=0;
|
||||
w.calcPrefSize(&pw, &ph);
|
||||
w.layout(0, 0, pw, ph);
|
||||
writeProperties(cout, &props, true);
|
||||
}
|
371
src/GUI/layout.cxx
Normal file
371
src/GUI/layout.cxx
Normal file
|
@ -0,0 +1,371 @@
|
|||
#include "layout.hxx"
|
||||
|
||||
// This file contains the actual layout engine. It has no dependence
|
||||
// on outside libraries; see layout-props.cxx for the glue code.
|
||||
|
||||
// Note, property names with leading double-underscores (__bx, etc...)
|
||||
// are debugging information, and can be safely removed.
|
||||
|
||||
const int DEFAULT_PADDING = 2;
|
||||
|
||||
int LayoutWidget::UNIT = 5;
|
||||
|
||||
bool LayoutWidget::eq(const char* a, const char* b)
|
||||
{
|
||||
while(*a && (*a == *b)) { a++; b++; }
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
// Normal widgets get a padding of 4 pixels. Layout groups shouldn't
|
||||
// have visible padding by default, except for top-level dialog groups
|
||||
// which need to leave two pixels for the puFrame's border. This
|
||||
// value can, of course, be overriden by the parent groups
|
||||
// <default-padding> property, or per widget with <padding>.
|
||||
int LayoutWidget::padding()
|
||||
{
|
||||
int pad = isType("group") ? 0 : 4;
|
||||
if(isType("dialog")) pad = 2;
|
||||
if(hasParent() && parent().hasField("default-padding"))
|
||||
pad = parent().getNum("default-padding");
|
||||
if(hasField("padding"))
|
||||
pad = getNum("padding");
|
||||
return pad;
|
||||
}
|
||||
|
||||
void LayoutWidget::calcPrefSize(int* w, int* h)
|
||||
{
|
||||
*w = *h = 0; // Ask for nothing by default
|
||||
|
||||
int legw = stringLength(getStr("legend"));
|
||||
int labw = stringLength(getStr("label"));
|
||||
|
||||
if(isType("dialog") || isType("group")) {
|
||||
if(!hasField("layout")) {
|
||||
// Legacy support for groups without layout managers.
|
||||
if(hasField("width")) *w = getNum("width");
|
||||
if(hasField("height")) *h = getNum("height");
|
||||
} else {
|
||||
const char* layout = getStr("layout");
|
||||
if (eq(layout, "hbox" )) doHVBox(false, false, w, h);
|
||||
else if(eq(layout, "vbox" )) doHVBox(false, true, w, h);
|
||||
else if(eq(layout, "table")) doTable(false, w, h);
|
||||
}
|
||||
} else if (isType("text")) {
|
||||
*w = labw;
|
||||
*h = 4*UNIT; // FIXME: multi line height?
|
||||
} else if (isType("button")) {
|
||||
*w = legw + 6*UNIT + (labw ? labw + UNIT : 0);
|
||||
*h = 6*UNIT;
|
||||
} else if (isType("checkbox") || isType("radio")) {
|
||||
*w = 3*UNIT + (labw ? (3*UNIT + labw) : 0);
|
||||
*h = 3*UNIT;
|
||||
} else if (isType("input") || isType("combo") || isType("select")) {
|
||||
*w = 17*UNIT;
|
||||
*h = 6*UNIT;
|
||||
} else if (isType("slider")) {
|
||||
if(getBool("vertical")) *w = 3*UNIT;
|
||||
else *h = 3*UNIT;
|
||||
} else if (isType("list") || isType("airport-list") || isType("dial")) {
|
||||
*w = *h = 12*UNIT;
|
||||
}
|
||||
|
||||
// Throw it all out if the user specified a fixed preference
|
||||
if(hasField("pref-width")) *w = getNum("pref-width");
|
||||
if(hasField("pref-height")) *h = getNum("pref-height");
|
||||
|
||||
// And finally correct for cell padding
|
||||
int pad = 2*padding();
|
||||
*w += pad;
|
||||
*h += pad;
|
||||
|
||||
// Store what we calculated
|
||||
setNum("__pw", *w);
|
||||
setNum("__ph", *h);
|
||||
}
|
||||
|
||||
// Set up geometry such that the widget lives "inside" the specified
|
||||
void LayoutWidget::layout(int x, int y, int w, int h)
|
||||
{
|
||||
setNum("__bx", x);
|
||||
setNum("__by", y);
|
||||
setNum("__bw", w);
|
||||
setNum("__bh", h);
|
||||
|
||||
// Correct for padding.
|
||||
int pad = padding();
|
||||
x += pad;
|
||||
y += pad;
|
||||
w -= 2*pad;
|
||||
h -= 2*pad;
|
||||
|
||||
int prefw = 0, prefh = 0;
|
||||
calcPrefSize(&prefw, &prefh);
|
||||
prefw -= 2*pad;
|
||||
prefh -= 2*pad;
|
||||
|
||||
// "Parent Set" values override widget preferences
|
||||
if(hasField("_psw")) prefw = getNum("_psw");
|
||||
if(hasField("_psh")) prefh = getNum("_psh");
|
||||
|
||||
bool isGroup = isType("dialog") || isType("group");
|
||||
|
||||
// Correct our box for alignment. The values above correspond to
|
||||
// a "fill" alignment.
|
||||
const char* halign = isGroup ? "fill" : "center";
|
||||
if(hasField("halign")) halign = getStr("halign");
|
||||
if(eq(halign, "left")) {
|
||||
w = prefw;
|
||||
} else if(eq(halign, "right")) {
|
||||
x += w - prefw;
|
||||
w = prefw;
|
||||
} else if(eq(halign, "center")) {
|
||||
x += (w - prefw)/2;
|
||||
w = prefw;
|
||||
}
|
||||
const char* valign = isGroup ? "fill" : "center";
|
||||
if(hasField("valign")) valign = getStr("valign");
|
||||
if(eq(valign, "bottom")) {
|
||||
h = prefh;
|
||||
} else if(eq(valign, "top")) {
|
||||
y += h - prefh;
|
||||
h = prefh;
|
||||
} else if(eq(valign, "center")) {
|
||||
y += (h - prefh)/2;
|
||||
h = prefh;
|
||||
}
|
||||
|
||||
// PUI widgets interpret their size differently depending on
|
||||
// type, so diddle the values as needed to fit the widget into
|
||||
// the x/y/w/h box we have calculated.
|
||||
if (isType("text")) {
|
||||
// puText labels are layed out to the right of the box, so
|
||||
// zero the width.
|
||||
w = 0;
|
||||
} else if (isType("input") || isType("combo") || isType("select")) {
|
||||
// Fix the height to a constant
|
||||
y += (h - 6*UNIT) / 2;
|
||||
h = 6*UNIT;
|
||||
} else if (isType("checkbox") || isType("radio")) {
|
||||
// The PUI dimensions are of the check area only. Center it
|
||||
// on the left side of our box.
|
||||
y += (h - 3*UNIT) / 2;
|
||||
w = h = 3*UNIT;
|
||||
} else if (isType("slider")) {
|
||||
// Fix the thickness to a constant
|
||||
if(getBool("vertical")) { x += (w-3*UNIT)/2; w = 3*UNIT; }
|
||||
else { y += (h-3*UNIT)/2; h = 3*UNIT; }
|
||||
}
|
||||
|
||||
// Set out output geometry
|
||||
setNum("x", x);
|
||||
setNum("y", y);
|
||||
setNum("width", w);
|
||||
setNum("height", h);
|
||||
|
||||
// Finally, if we are ourselves a layout object, do the actual layout.
|
||||
if(isGroup && hasField("layout")) {
|
||||
const char* layout = getStr("layout");
|
||||
if (eq(layout, "hbox" )) doHVBox(true, false);
|
||||
else if(eq(layout, "vbox" )) doHVBox(true, true);
|
||||
else if(eq(layout, "table")) doTable(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Convention: the "A" cooridinate refers to the major axis of the
|
||||
// container (width, for an hbox), "B" is minor.
|
||||
void LayoutWidget::doHVBox(bool doLayout, bool vertical, int* w, int* h)
|
||||
{
|
||||
int nc = nChildren();
|
||||
int* prefA = doLayout ? new int[nc] : 0;
|
||||
int i, totalA = 0, maxB = 0, nStretch = 0;
|
||||
int nEq = 0, eqA = 0, eqB = 0, eqTotalA = 0;
|
||||
for(i=0; i<nc; i++) {
|
||||
LayoutWidget child = getChild(i);
|
||||
int a, b;
|
||||
child.calcPrefSize(vertical ? &b : &a, vertical ? &a : &b);
|
||||
if(doLayout) prefA[i] = a;
|
||||
totalA += a;
|
||||
if(b > maxB) maxB = b;
|
||||
if(child.getBool("stretch")) {
|
||||
nStretch++;
|
||||
} else if(child.getBool("equal")) {
|
||||
int pad = child.padding();
|
||||
nEq++;
|
||||
eqTotalA += a - 2*pad;
|
||||
if(a-2*pad > eqA) eqA = a - 2*pad;
|
||||
if(b-2*pad > eqB) eqB = b - 2*pad;
|
||||
}
|
||||
}
|
||||
if(nStretch == 0) nStretch = nc;
|
||||
totalA += nEq * eqA - eqTotalA;
|
||||
|
||||
if(!doLayout) {
|
||||
if(vertical) { *w = maxB; *h = totalA; }
|
||||
else { *w = totalA; *h = maxB; }
|
||||
return;
|
||||
}
|
||||
|
||||
int currA = 0;
|
||||
int availA = getNum(vertical ? "height" : "width");
|
||||
int availB = getNum(vertical ? "width" : "height");
|
||||
bool stretchAll = nStretch == nc ? true : false;
|
||||
int stretch = availA - totalA;
|
||||
if(stretch < 0) stretch = 0;
|
||||
for(i=0; i<nc; i++) {
|
||||
// Swap the child order for vertical boxes, so we lay out
|
||||
// from top to bottom instead of along the cartesian Y axis.
|
||||
int idx = vertical ? (nc-i-1) : i;
|
||||
LayoutWidget child = getChild(idx);
|
||||
if(child.getBool("equal")) {
|
||||
int pad = child.padding();
|
||||
prefA[idx] = eqA + 2*pad;
|
||||
// Use "parent set" values to communicate the setting to
|
||||
// the child.
|
||||
child.setNum(vertical ? "_psh" : "_psw", eqA);
|
||||
child.setNum(vertical ? "_psw" : "_psh", eqB);
|
||||
}
|
||||
if(stretchAll || child.getBool("stretch")) {
|
||||
int chunk = stretch / nStretch;
|
||||
stretch -= chunk;
|
||||
nStretch--;
|
||||
prefA[idx] += chunk;
|
||||
child.setNum("__stretch", chunk);
|
||||
}
|
||||
if(vertical) child.layout( 0, currA, availB, prefA[idx]);
|
||||
else child.layout(currA, 0, prefA[idx], availB);
|
||||
currA += prefA[idx];
|
||||
}
|
||||
|
||||
delete[] prefA;
|
||||
}
|
||||
|
||||
struct TabCell {
|
||||
TabCell() {}
|
||||
LayoutWidget child;
|
||||
int w, h, row, col, rspan, cspan;
|
||||
};
|
||||
|
||||
void LayoutWidget::doTable(bool doLayout, int* w, int* h)
|
||||
{
|
||||
int i, j, nc = nChildren();
|
||||
TabCell* children = new TabCell[nc];
|
||||
|
||||
// Pass 1: initialize bookeeping structures
|
||||
int rows = 0, cols = 0;
|
||||
for(i=0; i<nc; i++) {
|
||||
TabCell* cell = &children[i];
|
||||
cell->child = getChild(i);
|
||||
cell->child.calcPrefSize(&cell->w, &cell->h);
|
||||
cell->row = cell->child.getNum("row");
|
||||
cell->col = cell->child.getNum("col");
|
||||
cell->rspan = cell->child.hasField("rowspan") ? cell->child.getNum("rowspan") : 1;
|
||||
cell->cspan = cell->child.hasField("colspan") ? cell->child.getNum("colspan") : 1;
|
||||
if(cell->row + cell->rspan > rows) rows = cell->row + cell->rspan;
|
||||
if(cell->col + cell->cspan > cols) cols = cell->col + cell->cspan;
|
||||
}
|
||||
int* rowSizes = new int[rows];
|
||||
int* colSizes = new int[cols];
|
||||
for(i=0; i<rows; i++) rowSizes[i] = 0;
|
||||
for(i=0; i<cols; i++) colSizes[i] = 0;
|
||||
|
||||
// Pass 1a (hack): we want row zero to be the top, not the
|
||||
// (cartesian: y==0) bottom, so reverse the sense of the row
|
||||
// numbers.
|
||||
for(i=0; i<nc; i++) {
|
||||
TabCell* cell = &children[i];
|
||||
cell->row = rows - cell->row - cell->rspan;
|
||||
}
|
||||
|
||||
// Pass 2: get sizes for single-cell children
|
||||
for(i=0; i<nc; i++) {
|
||||
TabCell* cell = &children[i];
|
||||
if(cell->rspan < 2 && cell->h > rowSizes[cell->row])
|
||||
rowSizes[cell->row] = cell->h;
|
||||
if(cell->cspan < 2 && cell->w > colSizes[cell->col])
|
||||
colSizes[cell->col] = cell->w;
|
||||
}
|
||||
|
||||
// Pass 3: multi-cell children, make space as needed
|
||||
for(i=0; i<nc; i++) {
|
||||
TabCell* cell = &children[i];
|
||||
if(cell->rspan > 1) {
|
||||
int total = 0;
|
||||
for(j=0; j<cell->rspan; j++)
|
||||
total += rowSizes[cell->row + j];
|
||||
int extra = total - cell->h;
|
||||
if(extra > 0) {
|
||||
for(j=0; j<cell->rspan; j++) {
|
||||
int chunk = extra / (cell->rspan - j);
|
||||
rowSizes[cell->row + j] += chunk;
|
||||
extra -= chunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(cell->cspan > 1) {
|
||||
int total = 0;
|
||||
for(j=0; j<cell->cspan; j++)
|
||||
total += colSizes[cell->col + j];
|
||||
int extra = total - cell->w;
|
||||
if(extra > 0) {
|
||||
for(j=0; j<cell->cspan; j++) {
|
||||
int chunk = extra / (cell->cspan - j);
|
||||
colSizes[cell->col + j] += chunk;
|
||||
extra -= chunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate our preferred sizes, and return if we aren't doing layout
|
||||
int prefw=0, prefh=0;
|
||||
for(i=0; i<cols; i++) prefw += colSizes[i];
|
||||
for(i=0; i<rows; i++) prefh += rowSizes[i];
|
||||
|
||||
if(!doLayout) {
|
||||
*w = prefw; *h = prefh;
|
||||
delete[] children; delete[] rowSizes; delete[] colSizes;
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate extra space
|
||||
int pad = 2*padding();
|
||||
int extra = getNum("height") - pad - prefh;
|
||||
for(i=0; i<rows; i++) {
|
||||
int chunk = extra / (rows - i);
|
||||
rowSizes[i] += chunk;
|
||||
extra -= chunk;
|
||||
}
|
||||
extra = getNum("width") - pad - prefw;
|
||||
for(i=0; i<cols; i++) {
|
||||
int chunk = extra / (cols - i);
|
||||
colSizes[i] += chunk;
|
||||
extra -= chunk;
|
||||
}
|
||||
|
||||
// Finally, lay out the children (with just two more temporary
|
||||
// arrays for calculating coordinates)
|
||||
int* rowY = new int[rows];
|
||||
int total = 0;
|
||||
for(i=0; i<rows; i++) { rowY[i] = total; total += rowSizes[i]; }
|
||||
|
||||
int* colX = new int[cols];
|
||||
total = 0;
|
||||
for(i=0; i<cols; i++) { colX[i] = total; total += colSizes[i]; }
|
||||
|
||||
for(i=0; i<nc; i++) {
|
||||
TabCell* cell = &children[i];
|
||||
int w = 0, h = 0;
|
||||
for(j=0; j<cell->rspan; j++) h += rowSizes[cell->row + j];
|
||||
for(j=0; j<cell->cspan; j++) w += colSizes[cell->col + j];
|
||||
int x = colX[cell->col];
|
||||
int y = rowY[cell->row];
|
||||
cell->child.layout(x, y, w, h);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
delete[] children;
|
||||
delete[] rowSizes;
|
||||
delete[] colSizes;
|
||||
delete[] rowY;
|
||||
delete[] colX;
|
||||
}
|
56
src/GUI/layout.hxx
Normal file
56
src/GUI/layout.hxx
Normal file
|
@ -0,0 +1,56 @@
|
|||
#ifndef __LAYOUT_HXX
|
||||
#define __LAYOUT_HXX
|
||||
|
||||
class SGPropertyNode;
|
||||
class puFont;
|
||||
|
||||
// For the purposes of doing layout management, widgets have a type,
|
||||
// zero or more children, and string-indexed "fields" which can be
|
||||
// constraints, parameters or x/y/width/height geometry values. It
|
||||
// can provide a "preferred" width and height to its parent, and is
|
||||
// capable of laying itself out into a specified x/y/w/h box. The
|
||||
// widget "type" is not a field for historical reasons having to do
|
||||
// with the way the dialog property format works.
|
||||
//
|
||||
// Note that this is a simple wrapper around an SGPropertyNode
|
||||
// pointer. The intent is that these objects will be created on the
|
||||
// stack as needed and passed by value. All persistent data is stored
|
||||
// in the wrapped properties.
|
||||
class LayoutWidget {
|
||||
public:
|
||||
static void setDefaultFont(puFont* font, int pixels);
|
||||
|
||||
LayoutWidget() { _prop = 0; }
|
||||
LayoutWidget(SGPropertyNode* p) { _prop = p; }
|
||||
|
||||
const char* type();
|
||||
bool hasParent();
|
||||
LayoutWidget parent();
|
||||
int nChildren();
|
||||
LayoutWidget getChild(int i);
|
||||
bool hasField(const char* f);
|
||||
int getNum(const char* f);
|
||||
bool getBool(const char* f);
|
||||
const char* getStr(const char* f);
|
||||
void setNum(const char* f, int num);
|
||||
|
||||
void calcPrefSize(int* w, int* h);
|
||||
void layout(int x, int y, int w, int h);
|
||||
|
||||
private:
|
||||
static int UNIT;
|
||||
static puFont FONT;
|
||||
|
||||
static bool eq(const char* a, const char* b);
|
||||
bool isType(const char* t) { return eq(t, type()); }
|
||||
|
||||
int padding();
|
||||
int stringLength(const char* s); // must handle null argument
|
||||
|
||||
void doHVBox(bool doLayout, bool vertical, int* w=0, int* h=0);
|
||||
void doTable(bool doLayout, int* w=0, int* h=0);
|
||||
|
||||
SGPropertyNode* _prop;
|
||||
};
|
||||
|
||||
#endif // __LAYOUT_HXX
|
Loading…
Add table
Reference in a new issue