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