8 #include <simgear/math/SGMath.hxx>
10 // This file contains the actual layout engine. It has no dependence
11 // on outside libraries; see layout-props.cxx for the glue code.
13 // Note, property names with leading double-underscores (__bx, etc...)
14 // are debugging information, and can be safely removed.
16 const int DEFAULT_PADDING = 2;
18 int LayoutWidget::UNIT = 5;
20 bool LayoutWidget::eq(const char* a, const char* b)
22 while(*a && (*a == *b)) { a++; b++; }
26 // Normal widgets get a padding of 4 pixels. Layout groups shouldn't
27 // have visible padding by default, except for top-level dialog groups
28 // which need to leave two pixels for the puFrame's border. This
29 // value can, of course, be overriden by the parent groups
30 // <default-padding> property, or per widget with <padding>.
31 int LayoutWidget::padding()
33 int pad = (isType("group") || isType("frame")) ? 0 : 4;
34 // As comments above note, this was being set to 2. For some
35 // reason this causes the dialogs to shrink on subsequent pops
36 // so for now we'll make "dialog" padding 0.
37 if(isType("dialog")) pad = 0;
38 if(hasParent() && parent().hasField("default-padding"))
39 pad = parent().getNum("default-padding");
40 if(hasField("padding"))
41 pad = getNum("padding");
45 void LayoutWidget::calcPrefSize(int* w, int* h)
47 *w = *h = 0; // Ask for nothing by default
49 if (!getBool("enabled", true) || isType("nasal"))
52 int legw = stringLength(getStr("legend"));
53 int labw = stringLength(getStr("label"));
55 if(isType("dialog") || isType("group") || isType("frame")) {
56 if(!hasField("layout")) {
57 // Legacy support for groups without layout managers.
58 if(hasField("width")) *w = getNum("width");
59 if(hasField("height")) *h = getNum("height");
61 const char* layout = getStr("layout");
62 if (eq(layout, "hbox" )) doHVBox(false, false, w, h);
63 else if(eq(layout, "vbox" )) doHVBox(false, true, w, h);
64 else if(eq(layout, "table")) doTable(false, w, h);
66 } else if (isType("text")) {
68 *h = 3*UNIT; // FIXME: multi line height?
69 } else if (isType("button")) {
70 *w = legw + 6*UNIT + (labw ? labw + UNIT : 0);
72 } else if (isType("checkbox") || isType("radio")) {
73 *w = 3*UNIT + (labw ? (3*UNIT + labw) : 0);
75 } else if (isType("input") || isType("combo") || isType("select")) {
78 } else if (isType("slider")) {
80 if(getBool("vertical")) *w = 4*UNIT;
82 } else if (isType("list") || isType("airport-list")
83 || isType("property-list") || isType("dial") || isType("waypointlist")) {
85 } else if (isType("hrule")) {
87 } else if (isType("vrule")) {
91 // Throw it all out if the user specified a fixed preference
92 if(hasField("pref-width")) *w = getNum("pref-width");
93 if(hasField("pref-height")) *h = getNum("pref-height");
95 // And finally correct for cell padding
96 int pad = 2*padding();
100 // Store what we calculated
105 // Set up geometry such that the widget lives "inside" the specified
106 void LayoutWidget::layout(int x, int y, int w, int h)
108 if (!getBool("enabled", true) || isType("nasal"))
116 // Correct for padding.
123 int prefw = 0, prefh = 0;
124 calcPrefSize(&prefw, &prefh);
128 // "Parent Set" values override widget preferences
129 if(hasField("_psw")) prefw = getNum("_psw");
130 if(hasField("_psh")) prefh = getNum("_psh");
132 bool isGroup = isType("dialog") || isType("group") || isType("frame");
134 // Correct our box for alignment. The values above correspond to
135 // a "fill" alignment.
136 const char* halign = (isGroup || isType("hrule")) ? "fill" : "center";
137 if(hasField("halign")) halign = getStr("halign");
138 if(eq(halign, "left")) {
140 } else if(eq(halign, "right")) {
143 } else if(eq(halign, "center")) {
147 const char* valign = (isGroup || isType("vrule")) ? "fill" : "center";
148 if(hasField("valign")) valign = getStr("valign");
149 if(eq(valign, "bottom")) {
151 } else if(eq(valign, "top")) {
154 } else if(eq(valign, "center")) {
159 // PUI widgets interpret their size differently depending on
160 // type, so diddle the values as needed to fit the widget into
161 // the x/y/w/h box we have calculated.
162 if (isType("text")) {
163 // puText labels are layed out to the right of the box, so
164 // zero the width. Also subtract PUSTR_RGAP from the x
165 // coordinate to compensate for the added gap between the
166 // label and its empty puObject.
169 } else if (isType("input") || isType("combo") || isType("select")) {
170 // Fix the height to a constant
171 y += (h - 6*UNIT) / 2;
173 } else if (isType("checkbox") || isType("radio")) {
174 // The PUI dimensions are of the check area only. Center it
175 // on the left side of our box.
176 y += (h - 3*UNIT) / 2;
178 } else if (isType("slider")) {
179 // Fix the thickness to a constant
180 if(getBool("vertical")) { x += (w-4*UNIT)/2; w = 4*UNIT; }
181 else { y += (h-4*UNIT)/2; h = 4*UNIT; }
184 // Set out output geometry
190 // Finally, if we are ourselves a layout object, do the actual layout.
191 if(isGroup && hasField("layout")) {
192 const char* layout = getStr("layout");
193 if (eq(layout, "hbox" )) doHVBox(true, false);
194 else if(eq(layout, "vbox" )) doHVBox(true, true);
195 else if(eq(layout, "table")) doTable(true);
199 // Convention: the "A" cooridinate refers to the major axis of the
200 // container (width, for an hbox), "B" is minor.
201 void LayoutWidget::doHVBox(bool doLayout, bool vertical, int* w, int* h)
203 int nc = nChildren();
204 int* prefA = doLayout ? new int[nc] : 0;
205 int i, totalA = 0, maxB = 0, nStretch = 0;
206 int nEq = 0, eqA = 0, eqB = 0, eqTotalA = 0;
207 for(i=0; i<nc; i++) {
208 LayoutWidget child = getChild(i);
209 if (!child.getBool("enabled", true))
213 child.calcPrefSize(vertical ? &b : &a, vertical ? &a : &b);
214 if(doLayout) prefA[i] = a;
216 if(b > maxB) maxB = b;
217 if(child.getBool("stretch")) {
219 } else if(child.getBool("equal")) {
220 int pad = child.padding();
222 eqTotalA += a - 2*pad;
223 if(a-2*pad > eqA) eqA = a - 2*pad;
224 if(b-2*pad > eqB) eqB = b - 2*pad;
227 if(nStretch == 0) nStretch = nc;
228 totalA += nEq * eqA - eqTotalA;
231 if(vertical) { *w = maxB; *h = totalA; }
232 else { *w = totalA; *h = maxB; }
237 int availA = getNum(vertical ? "height" : "width");
238 int availB = getNum(vertical ? "width" : "height");
239 bool stretchAll = nStretch == nc ? true : false;
240 int stretch = availA - totalA;
241 if(stretch < 0) stretch = 0;
242 for(i=0; i<nc; i++) {
243 // Swap the child order for vertical boxes, so we lay out
244 // from top to bottom instead of along the cartesian Y axis.
245 int idx = vertical ? (nc-i-1) : i;
246 LayoutWidget child = getChild(idx);
247 if (!child.getBool("enabled", true))
250 if(child.getBool("equal")) {
251 int pad = child.padding();
252 prefA[idx] = eqA + 2*pad;
253 // Use "parent set" values to communicate the setting to
255 child.setNum(vertical ? "_psh" : "_psw", eqA);
256 child.setNum(vertical ? "_psw" : "_psh", eqB);
258 if(stretchAll || child.getBool("stretch")) {
259 int chunk = stretch / nStretch;
263 child.setNum("__stretch", chunk);
265 if(vertical) child.layout( 0, currA, availB, prefA[idx]);
266 else child.layout(currA, 0, prefA[idx], availB);
276 int w, h, row, col, rspan, cspan;
279 void LayoutWidget::doTable(bool doLayout, int* w, int* h)
281 int i, j, nc = nChildren();
282 TabCell* children = new TabCell[nc];
284 // Pass 1: initialize bookeeping structures
285 int rows = 0, cols = 0;
286 for(i=0; i<nc; i++) {
287 TabCell* cell = &children[i];
288 cell->child = getChild(i);
289 cell->child.calcPrefSize(&cell->w, &cell->h);
290 cell->row = cell->child.getNum("row");
291 cell->col = cell->child.getNum("col");
292 cell->rspan = cell->child.hasField("rowspan") ? cell->child.getNum("rowspan") : 1;
293 cell->cspan = cell->child.hasField("colspan") ? cell->child.getNum("colspan") : 1;
294 if(cell->row + cell->rspan > rows) rows = cell->row + cell->rspan;
295 if(cell->col + cell->cspan > cols) cols = cell->col + cell->cspan;
297 int* rowSizes = new int[rows];
298 int* colSizes = new int[cols];
299 for(i=0; i<rows; i++) rowSizes[i] = 0;
300 for(i=0; i<cols; i++) colSizes[i] = 0;
302 // Pass 1a (hack): we want row zero to be the top, not the
303 // (cartesian: y==0) bottom, so reverse the sense of the row
305 for(i=0; i<nc; i++) {
306 TabCell* cell = &children[i];
307 cell->row = rows - cell->row - cell->rspan;
310 // Pass 2: get sizes for single-cell children
311 for(i=0; i<nc; i++) {
312 TabCell* cell = &children[i];
313 if(cell->rspan < 2 && cell->h > rowSizes[cell->row])
314 rowSizes[cell->row] = cell->h;
315 if(cell->cspan < 2 && cell->w > colSizes[cell->col])
316 colSizes[cell->col] = cell->w;
319 // Pass 3: multi-cell children, make space as needed
320 for(i=0; i<nc; i++) {
321 TabCell* cell = &children[i];
322 if(cell->rspan > 1) {
324 for(j=0; j<cell->rspan; j++)
325 total += rowSizes[cell->row + j];
326 int extra = cell->h - total;
328 for(j=0; j<cell->rspan; j++) {
329 int chunk = extra / (cell->rspan - j);
330 rowSizes[cell->row + j] += chunk;
335 if(cell->cspan > 1) {
337 for(j=0; j<cell->cspan; j++)
338 total += colSizes[cell->col + j];
339 int extra = cell->w - total;
341 for(j=0; j<cell->cspan; j++) {
342 int chunk = extra / (cell->cspan - j);
343 colSizes[cell->col + j] += chunk;
350 // Calculate our preferred sizes, and return if we aren't doing layout
351 int prefw=0, prefh=0;
352 for(i=0; i<cols; i++) prefw += colSizes[i];
353 for(i=0; i<rows; i++) prefh += rowSizes[i];
356 *w = prefw; *h = prefh;
357 delete[] children; delete[] rowSizes; delete[] colSizes;
361 // Allocate extra space
362 int pad = 2*padding();
363 int extra = getNum("height") - pad - prefh;
364 for(i=0; i<rows; i++) {
365 int chunk = extra / (rows - i);
366 rowSizes[i] += chunk;
369 extra = getNum("width") - pad - prefw;
370 for(i=0; i<cols; i++) {
371 int chunk = extra / (cols - i);
372 colSizes[i] += chunk;
376 // Finally, lay out the children (with just two more temporary
377 // arrays for calculating coordinates)
378 int* rowY = new int[rows];
380 for(i=0; i<rows; i++) { rowY[i] = total; total += rowSizes[i]; }
382 int* colX = new int[cols];
384 for(i=0; i<cols; i++) { colX[i] = total; total += colSizes[i]; }
386 for(i=0; i<nc; i++) {
387 TabCell* cell = &children[i];
389 for(j=0; j<cell->rspan; j++) h += rowSizes[cell->row + j];
390 for(j=0; j<cell->cspan; j++) w += colSizes[cell->col + j];
391 int x = colX[cell->col];
392 int y = rowY[cell->row];
393 cell->child.layout(x, y, w, h);