]> git.mxchange.org Git - flightgear.git/blob - src/GUI/layout.cxx
GUI support for VIA/Discontinuity
[flightgear.git] / src / GUI / layout.cxx
1
2 #ifdef HAVE_CONFIG_H
3 #  include <config.h>
4 #endif
5
6 #include "layout.hxx"
7
8
9 // This file contains the actual layout engine.  It has no dependence
10 // on outside libraries; see layout-props.cxx for the glue code.
11
12 // Note, property names with leading double-underscores (__bx, etc...)
13 // are debugging information, and can be safely removed.
14
15 int LayoutWidget::UNIT = 5;
16
17 bool LayoutWidget::eq(const char* a, const char* b)
18 {
19     while(*a && (*a == *b)) { a++; b++; }
20     return *a == *b;
21 }
22
23 // Normal widgets get a padding of 4 pixels.  Layout groups shouldn't
24 // have visible padding by default, except for top-level dialog groups
25 // which need to leave two pixels for the puFrame's border.  This
26 // value can, of course, be overriden by the parent groups
27 // <default-padding> property, or per widget with <padding>.
28 int LayoutWidget::padding()
29 {
30     int pad = (isType("group") || isType("frame")) ? 0 : 4;
31     // As comments above note,  this was being set to 2.  For some
32     // reason this causes the dialogs to shrink on subsequent pops
33     // so for now we'll make "dialog" padding 0.
34     if(isType("dialog")) pad = 0;
35     if(hasParent() && parent().hasField("default-padding"))
36         pad = parent().getNum("default-padding");
37     if(hasField("padding"))
38         pad = getNum("padding");
39     return pad;
40 }
41
42 void LayoutWidget::calcPrefSize(int* w, int* h)
43 {
44     *w = *h = 0; // Ask for nothing by default
45
46     if (!getBool("enabled", true) || isType("nasal"))
47         return;
48
49     int legw = stringLength(getStr("legend"));
50     int labw = stringLength(getStr("label"));
51
52     if(isType("dialog") || isType("group") || isType("frame")) {
53         if(!hasField("layout")) {
54             // Legacy support for groups without layout managers.
55             if(hasField("width")) *w = getNum("width");
56             if(hasField("height")) *h = getNum("height");
57         } else {
58             const char* layout = getStr("layout");
59             if     (eq(layout, "hbox" )) doHVBox(false, false, w, h);
60             else if(eq(layout, "vbox" )) doHVBox(false, true, w, h);
61             else if(eq(layout, "table")) doTable(false, w, h);
62         }
63     } else if (isType("text")) {
64         *w = labw;
65         *h = 3*UNIT; // FIXME: multi line height?
66     } else if (isType("button")) {
67         *w = legw + 6*UNIT + (labw ? labw + UNIT : 0);
68         *h = 6*UNIT;
69     } else if (isType("checkbox") || isType("radio")) {
70         *w = 3*UNIT + (labw ? (3*UNIT + labw) : 0);
71         *h = 3*UNIT;
72     } else if (isType("input") || isType("combo") || isType("select")) {
73         *w = 17*UNIT;
74         *h = 6*UNIT;
75     } else if (isType("slider")) {
76         *w = *h = 17*UNIT;
77         if(getBool("vertical")) *w = 4*UNIT;
78         else                    *h = 4*UNIT;
79     } else if (isType("list") || isType("airport-list")
80             || isType("property-list") || isType("dial") || isType("waypointlist")) {
81         *w = *h = 12*UNIT;
82     } else if (isType("hrule")) {
83         *h = 1;
84     } else if (isType("vrule")) {
85         *w = 1;
86     }
87
88     // Throw it all out if the user specified a fixed preference
89     if(hasField("pref-width"))  *w = getNum("pref-width");
90     if(hasField("pref-height")) *h = getNum("pref-height");
91
92     // And finally correct for cell padding
93     int pad = 2*padding();
94     *w += pad;
95     *h += pad;
96
97     // Store what we calculated
98     setNum("__pw", *w);
99     setNum("__ph", *h);
100 }
101
102 // Set up geometry such that the widget lives "inside" the specified 
103 void LayoutWidget::layout(int x, int y, int w, int h)
104 {
105     if (!getBool("enabled", true) || isType("nasal"))
106         return;
107
108     setNum("__bx", x);
109     setNum("__by", y);
110     setNum("__bw", w);
111     setNum("__bh", h);
112
113     // Correct for padding.
114     int pad = padding();
115     x += pad;
116     y += pad;
117     w -= 2*pad;
118     h -= 2*pad;
119
120     int prefw = 0, prefh = 0;
121     calcPrefSize(&prefw, &prefh);
122     prefw -= 2*pad;
123     prefh -= 2*pad;
124
125     // "Parent Set" values override widget preferences
126     if(hasField("_psw")) prefw = getNum("_psw");
127     if(hasField("_psh")) prefh = getNum("_psh");
128
129     bool isGroup = isType("dialog") || isType("group") || isType("frame");
130
131     // Correct our box for alignment.  The values above correspond to
132     // a "fill" alignment.
133     const char* halign = (isGroup || isType("hrule")) ? "fill" : "center";
134     if(hasField("halign")) halign = getStr("halign");
135     if(eq(halign, "left")) {
136         w = prefw;
137     } else if(eq(halign, "right")) {
138         x += w - prefw;
139         w = prefw;
140     } else if(eq(halign, "center")) {
141         x += (w - prefw)/2;
142         w = prefw;
143     }
144     const char* valign = (isGroup || isType("vrule")) ? "fill" : "center";
145     if(hasField("valign")) valign = getStr("valign");
146     if(eq(valign, "bottom")) {
147         h = prefh;
148     } else if(eq(valign, "top")) {
149         y += h - prefh;
150         h = prefh;
151     } else if(eq(valign, "center")) {
152         y += (h - prefh)/2;
153         h = prefh;
154     }
155
156     // PUI widgets interpret their size differently depending on
157     // type, so diddle the values as needed to fit the widget into
158     // the x/y/w/h box we have calculated.
159     if (isType("text")) {
160         // puText labels are layed out to the right of the box, so
161         // zero the width. Also subtract PUSTR_RGAP from the x
162         // coordinate to compensate for the added gap between the
163         // label and its empty puObject.
164         x -= 5;
165         w = 0;
166     } else if (isType("input") || isType("combo") || isType("select")) {
167         // Fix the height to a constant
168         y += (h - 6*UNIT) / 2;
169         h = 6*UNIT;
170     } else if (isType("checkbox") || isType("radio")) {
171         // The PUI dimensions are of the check area only.  Center it
172         // on the left side of our box.
173         y += (h - 3*UNIT) / 2;
174         w = h = 3*UNIT;
175     } else if (isType("slider")) {
176         // Fix the thickness to a constant
177         if(getBool("vertical")) { x += (w-4*UNIT)/2; w = 4*UNIT; }
178         else                    { y += (h-4*UNIT)/2; h = 4*UNIT; }
179     }
180
181     // Set out output geometry
182     setNum("x", x);
183     setNum("y", y);
184     setNum("width", w);
185     setNum("height", h);
186
187     // Finally, if we are ourselves a layout object, do the actual layout.
188     if(isGroup && hasField("layout")) {
189         const char* layout = getStr("layout");
190         if     (eq(layout, "hbox" )) doHVBox(true, false);
191         else if(eq(layout, "vbox" )) doHVBox(true, true);
192         else if(eq(layout, "table")) doTable(true);
193     }
194 }
195
196 // Convention: the "A" cooridinate refers to the major axis of the
197 // container (width, for an hbox), "B" is minor.
198 void LayoutWidget::doHVBox(bool doLayout, bool vertical, int* w, int* h)
199 {
200     int nc = nChildren();
201     int* prefA = doLayout ? new int[nc] : 0;
202     int i, totalA = 0, maxB = 0, nStretch = 0;
203     int nEq = 0, eqA = 0, eqB = 0, eqTotalA = 0;
204     for(i=0; i<nc; i++) {
205         LayoutWidget child = getChild(i);
206         if (!child.getBool("enabled", true))
207             continue;
208
209         int a, b;
210         child.calcPrefSize(vertical ? &b : &a, vertical ? &a : &b);
211         if(doLayout) prefA[i] = a;
212         totalA += a;
213         if(b > maxB) maxB = b;
214         if(child.getBool("stretch")) {
215             nStretch++;
216         } else if(child.getBool("equal")) {
217             int pad = child.padding();
218             nEq++;
219             eqTotalA += a - 2*pad;
220             if(a-2*pad > eqA) eqA = a - 2*pad;
221             if(b-2*pad > eqB) eqB = b - 2*pad;
222         }
223     }
224     if(nStretch == 0) nStretch = nc;
225     totalA += nEq * eqA - eqTotalA; 
226
227     if(!doLayout) {
228         if(vertical) { *w = maxB;   *h = totalA; }
229         else         { *w = totalA; *h = maxB; }
230         return;
231     }
232
233     int currA = 0;
234     int availA = getNum(vertical ? "height" : "width");
235     int availB = getNum(vertical ? "width" : "height");
236     bool stretchAll = nStretch == nc ? true : false;
237     int stretch = availA - totalA;
238     if(stretch < 0) stretch = 0;
239     for(i=0; i<nc; i++) {
240         // Swap the child order for vertical boxes, so we lay out
241         // from top to bottom instead of along the cartesian Y axis.
242         int idx = vertical ? (nc-i-1) : i;
243         LayoutWidget child = getChild(idx);
244         if (!child.getBool("enabled", true))
245             continue;
246
247         if(child.getBool("equal")) {
248             int pad = child.padding();
249             prefA[idx] = eqA + 2*pad;
250             // Use "parent set" values to communicate the setting to
251             // the child.
252             child.setNum(vertical ? "_psh" : "_psw", eqA);
253             child.setNum(vertical ? "_psw" : "_psh", eqB);
254         }
255         if(stretchAll || child.getBool("stretch")) {
256             int chunk = stretch / nStretch;
257             stretch -= chunk;
258             nStretch--;
259             prefA[idx] += chunk;
260             child.setNum("__stretch", chunk);
261         }
262         if(vertical) child.layout(    0, currA,   availB, prefA[idx]);
263         else         child.layout(currA,     0, prefA[idx],   availB);
264         currA += prefA[idx];
265     }
266
267     delete[] prefA;
268 }
269
270 struct TabCell {
271     TabCell() {}
272     LayoutWidget child;
273     int w, h, row, col, rspan, cspan;
274 };
275
276 void LayoutWidget::doTable(bool doLayout, int* w, int* h)
277 {
278     int i, j, nc = nChildren();
279     TabCell* children = new TabCell[nc];
280     
281     // Pass 1: initialize bookeeping structures
282     int rows = 0, cols = 0;
283     for(i=0; i<nc; i++) {
284         TabCell* cell = &children[i];
285         cell->child = getChild(i);
286         cell->child.calcPrefSize(&cell->w, &cell->h);
287         cell->row = cell->child.getNum("row");
288         cell->col = cell->child.getNum("col");
289         cell->rspan = cell->child.hasField("rowspan") ? cell->child.getNum("rowspan") : 1;
290         cell->cspan = cell->child.hasField("colspan") ? cell->child.getNum("colspan") : 1;
291         if(cell->row + cell->rspan > rows) rows = cell->row + cell->rspan;
292         if(cell->col + cell->cspan > cols) cols = cell->col + cell->cspan;
293     }
294     int* rowSizes = new int[rows];
295     int* colSizes = new int[cols];
296     for(i=0; i<rows; i++) rowSizes[i] = 0;
297     for(i=0; i<cols; i++) colSizes[i] = 0;
298
299     // Pass 1a (hack): we want row zero to be the top, not the
300     // (cartesian: y==0) bottom, so reverse the sense of the row
301     // numbers.
302     for(i=0; i<nc; i++) {
303         TabCell* cell = &children[i];
304         cell->row = rows - cell->row - cell->rspan;
305     }
306     
307     // Pass 2: get sizes for single-cell children
308     for(i=0; i<nc; i++) {
309         TabCell* cell = &children[i];
310         if(cell->rspan < 2 && cell->h > rowSizes[cell->row])
311             rowSizes[cell->row] = cell->h;
312         if(cell->cspan < 2 && cell->w > colSizes[cell->col])
313             colSizes[cell->col] = cell->w;
314     }
315     
316     // Pass 3: multi-cell children, make space as needed
317     for(i=0; i<nc; i++) {
318         TabCell* cell = &children[i];
319         if(cell->rspan > 1) {
320             int total = 0;
321             for(j=0; j<cell->rspan; j++)
322                 total += rowSizes[cell->row + j];
323             int extra = cell->h - total;
324             if(extra > 0) {
325                 for(j=0; j<cell->rspan; j++) {
326                     int chunk = extra / (cell->rspan - j);
327                     rowSizes[cell->row + j] += chunk;
328                     extra -= chunk;
329                 }
330             }
331         }
332         if(cell->cspan > 1) {
333             int total = 0;
334             for(j=0; j<cell->cspan; j++)
335                 total += colSizes[cell->col + j];
336             int extra = cell->w - total;
337             if(extra > 0) {
338                 for(j=0; j<cell->cspan; j++) {
339                     int chunk = extra / (cell->cspan - j);
340                     colSizes[cell->col + j] += chunk;
341                     extra -= chunk;
342                 }
343             }
344         }
345     }
346
347     // Calculate our preferred sizes, and return if we aren't doing layout
348     int prefw=0, prefh=0;
349     for(i=0; i<cols; i++) prefw += colSizes[i];
350     for(i=0; i<rows; i++) prefh += rowSizes[i];
351
352     if(!doLayout) {
353         *w = prefw; *h = prefh;
354         delete[] children; delete[] rowSizes; delete[] colSizes;
355         return;
356     }
357
358     // Allocate extra space
359     int pad = 2*padding();
360     int extra = getNum("height") - pad - prefh;
361     for(i=0; i<rows; i++) {
362         int chunk = extra / (rows - i);
363         rowSizes[i] += chunk;
364         extra -= chunk;
365     }
366     extra = getNum("width") - pad - prefw;
367     for(i=0; i<cols; i++) {
368         int chunk = extra / (cols - i);
369         colSizes[i] += chunk;
370         extra -= chunk;
371     }
372
373     // Finally, lay out the children (with just two more temporary
374     // arrays for calculating coordinates)
375     int* rowY = new int[rows];
376     int total = 0;
377     for(i=0; i<rows; i++) { rowY[i] = total; total += rowSizes[i]; }
378
379     int* colX = new int[cols];
380     total = 0;
381     for(i=0; i<cols; i++) { colX[i] = total; total += colSizes[i]; }
382
383     for(i=0; i<nc; i++) {
384         TabCell* cell = &children[i];
385         int w = 0, h = 0;
386         for(j=0; j<cell->rspan; j++) h += rowSizes[cell->row + j];
387         for(j=0; j<cell->cspan; j++) w += colSizes[cell->col + j];
388         int x = colX[cell->col];
389         int y = rowY[cell->row];
390         cell->child.layout(x, y, w, h);
391     }    
392
393     // Clean up
394     delete[] children;
395     delete[] rowSizes;
396     delete[] colSizes;
397     delete[] rowY;
398     delete[] colX;
399 }