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