comparison mde/gui/widget/layout.d @ 37:052df9b2fe07

Allowed widget resizing, changed widget IDs and made Input catch any callback exceptions. Enabled widget resizing. Removed IRenderer's temporary drawBox method and added drawButton for ButtonWidget. Made the Widget class abstract and added FixedWidget and SizableWidget classes. Rewrote much of createWidget to use meta-code; changed widget IDs. Made Input catch callback exceptions and report error messages. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Mon, 05 May 2008 14:47:25 +0100
parents 6b4116e6355c
children 8c4c96f04e7f
comparison
equal deleted inserted replaced
36:57d000574d75 37:052df9b2fe07
17 module mde.gui.widget.layout; 17 module mde.gui.widget.layout;
18 18
19 import mde.gui.widget.Widget; 19 import mde.gui.widget.Widget;
20 import mde.gui.exception : WidgetDataException; 20 import mde.gui.exception : WidgetDataException;
21 21
22 /// Encapsulates a grid of Widgets 22 /** Encapsulates a grid of Widgets.
23 class GridWidget : Widget 23 *
24 * Since a grid with either dimension zero is not useful, there must be at least one sub-widget. */
25 class GridLayoutWidget : Widget
24 { 26 {
25 this (IWindow wind, IWidget, int[] data) { 27 this (IWindow wind, IWidget, int[] data) {
26 // Get grid size 28 // Get grid size and check data
27 if (data.length < 2) throw new WidgetDataException; 29 // Check sufficient data for rows, cols, and at least one widget:
30 if (data.length < 3) throw new WidgetDataException;
28 rows = data[0]; 31 rows = data[0];
29 cols = data[1]; 32 cols = data[1];
33 if (data.length != 2 + rows * cols) throw new WidgetDataException;
34 /* data.length >= 3 so besides checking the length is correct, this tells us:
35 * rows * cols >= 3 - 2 = 1 a free check!
36 * The only thing not checked is whether both rows and cols are negative, which would
37 * cause an exception when dynamic arrays are allocated later (and is unlikely). */
30 38
31 window = wind; 39 window = wind;
32 40
33 // Get all sub-widgets 41 // Get all sub-widgets
34 // Check: correct data length and rows*cols >= 0 (know data.length - 2 >= 0).
35 if (data.length != 2 + rows * cols) throw new WidgetDataException;
36 subWidgets.length = rows*cols; 42 subWidgets.length = rows*cols;
37 foreach (i, inout subWidget; subWidgets) { 43 foreach (i, inout subWidget; subWidgets) {
38 subWidget = window.makeWidget (data[i+2], this); 44 subWidget = window.makeWidget (data[i+2], this);
39 } 45 }
40 46 }
41 getMinimumSize (w,h); // Calculate the size (current size is not saved) 47
42 } 48 bool isWSizable () {
43 49 if (colSizable == -2) { // check whether any columns are resizable
44 // Calculates from all rows and columns of widgets. 50 for1:
45 void getMinimumSize (out int w, out int h) { 51 for (uint i = 0; i < cols; ++i) { // for each column
46 if (rows*cols == 0) { // special case 52 for (uint j = 0; j < subWidgets.length; j += cols) // for each row
47 w = h = 0; 53 if (!subWidgets[i+j].isWSizable) // column not resizable
48 return; 54 continue for1; // continue the outer for loop
49 } 55
50 56 // column is resizable if we get to here
51 // Find the sizes of all subWidgets 57 colSizable = i;
52 int[] widgetW = new int[subWidgets.length]; // dimensions 58 goto break1; // use goto in lieu of for...else
53 int[] widgetH = new int[subWidgets.length]; 59 }
54 foreach (i,widget; subWidgets) widget.getCurrentSize (widgetW[i],widgetH[i]); 60
55 61 // if we get here, no resizable column was found
56 // Find row heights and column widths (non cumulative) 62 colSizable = -1;
57 rowH.length = rows; 63
58 colW.length = cols; //WARNING: code reliant on these being initialised to zero 64 break1:;
59 for (uint i = 0; i < subWidgets.length; ++i) { 65 }
60 uint n = i / cols; // row 66
61 if (rowH[n] < widgetH[i]) rowH[n] = widgetH[i]; 67 if (colSizable >= 0) return true;
62 n = i % cols; // column 68 else return false;
63 if (colW[n] < widgetW[i]) colW[n] = widgetW[i]; 69 }
64 } 70
65 71 bool isHSizable () {
66 // rowY / colX 72 if (rowSizable == -2) { // check whether any columns are resizable
73 for2:
74 for (uint i = 0; i < subWidgets.length; i += cols) { // for each row
75 for (uint j = 0; j < cols; ++j) // for each column
76 if (!subWidgets[i+j].isHSizable)
77 continue for2;
78
79 rowSizable = i / cols; // the current row
80 goto break2;
81 }
82
83 rowSizable = -1;
84
85 break2:;
86 }
87
88 if (rowSizable >= 0) return true;
89 else return false;
90 }
91
92 /* Calculates the minimal size from all rows and columns of widgets. */
93 void getMinimalSize (out int mw, out int mh) {
94 // If rowHMin & colWMin are null, calculate them. They are set null whenever the contents
95 // or the contents' minimal size change, as well as when this widget is created.
96 if (rowHMin is null)
97 genMinRowColSizes;
98
99 // Calculate the size, starting with the spacing:
100 mh = window.renderer.layoutSpacing; // use temporarily
101 mw = mh * (cols - 1);
102 mh *= (rows - 1);
103
104 foreach (x; colWMin) // add the column/row's dimensions
105 mw += x;
106 foreach (x; rowHMin)
107 mh += x;
108 }
109
110 void setSize (int nw, int nh) {
111 // Step 1: calculate the minimal row/column sizes.
112 int mw, mh; // FIXME: use w,h directly?
113 getMinimalSize (mw, mh);
114 colW = colWMin; // start with these dimensions, and increase if necessary
115 rowH = rowHMin;
116
117 // Step 2: clamp nw/nh or expand a column/row to achieve the required size
118 if (nw <= mw) nw = mw; // clamp to minimal size
119 else {
120 if (isWSizable) // calculates colSizable; true if any is resizable
121 colW[colSizable] += nw - mw; // new width
122 else // no resizable column; so force the last one
123 colW[$-1] += nw - mw;
124 }
125
126 if (nh <= mh) nh = mh;
127 else {
128 if (isHSizable)
129 rowH[rowSizable] += nh - mh;
130 else
131 rowH[$-1] += nh - mh;
132 }
133
134 // Step 3: set each sub-widget's size.
135 foreach (i,widget; subWidgets)
136 widget.setSize (colW[i % cols], rowH[i / cols]);
137
138 // Step 4: calculate the column and row positions
139 colX.length = cols;
67 rowY.length = rows; 140 rowY.length = rows;
68 colX.length = cols;
69 int spacing = window.renderer.layoutSpacing; 141 int spacing = window.renderer.layoutSpacing;
70 142
71 int cum = 0; 143 int cum = 0;
72 foreach (i, x; rowH) { 144 foreach (i, x; rowH) {
73 rowY[i] = cum; 145 rowY[i] = cum;
74 cum += x + spacing; 146 cum += x + spacing;
75 } 147 }
76 h = cum - spacing; // total height 148 h = cum - spacing; // set the total height
149 assert (h == nh); // FIXME: remove and set w/h directly once this is asserted
150
77 cum = 0; 151 cum = 0;
78 foreach (i, x; colW) { 152 foreach (i, x; colW) {
79 colX[i] = cum; 153 colX[i] = cum;
80 cum += x + spacing; 154 cum += x + spacing;
81 } 155 }
82 w = cum - spacing; // total width 156 w = cum - spacing; // total width
157 assert (w == nw);
83 } 158 }
84 159
85 void setPosition (int x, int y) { 160 void setPosition (int x, int y) {
86 this.x = x; 161 this.x = x;
87 this.y = y; 162 this.y = y;
88 163
89 foreach (i,widget; subWidgets) 164 foreach (i,widget; subWidgets)
90 widget.setPosition (x + colX[i % cols], y + rowY[i / cols]); 165 widget.setPosition (x + colX[i % cols], y + rowY[i / cols]);
91 } 166 }
92 167
168
93 // Find the relevant widget. 169 // Find the relevant widget.
94 IWidget getWidget (int cx, int cy) { 170 IWidget getWidget (int cx, int cy) {
95 if (rows*cols == 0) return this; // special case
96
97 int lx = cx - x, ly = cy - y; // use coords relative to this widget 171 int lx = cx - x, ly = cy - y; // use coords relative to this widget
98 172
99 // Find the column 173 // Find the column
100 int i = cols - 1; // starting from right... 174 int i = cols - 1; // starting from right...
101 while (lx < colX[i]) { // decrement while left of this column 175 while (lx < colX[i]) { // decrement while left of this column
117 ly -= rowY[j]; 191 ly -= rowY[j];
118 IWidget widg = subWidgets[i + j*cols]; 192 IWidget widg = subWidgets[i + j*cols];
119 widg.getCurrentSize (i,j); 193 widg.getCurrentSize (i,j);
120 if (lx < i && ly < j) 194 if (lx < i && ly < j)
121 return widg.getWidget (cx, cy); 195 return widg.getWidget (cx, cy);
196 return this; // wasn't in cell
122 } 197 }
123 198
124 void draw () { 199 void draw () {
125 super.draw (); 200 super.draw ();
126 201
127 foreach (widget; subWidgets) 202 foreach (widget; subWidgets)
128 widget.draw (); 203 widget.draw ();
129 } 204 }
130 205
206 private:
207 void genMinRowColSizes () {
208 // Find the sizes of all subWidgets
209 int[] widgetW = new int[subWidgets.length]; // dimensions
210 int[] widgetH = new int[subWidgets.length];
211 foreach (i,widget; subWidgets)
212 widget.getMinimalSize (widgetW[i],widgetH[i]);
213
214 // Find the minimal row heights and column widths (non cumulative)
215 colWMin = new int[cols]; // set length
216 rowHMin = new int[rows];
217 for (uint i = 0; i < subWidgets.length; ++i) {
218 uint n;
219 n = i % cols; // column
220 if (colWMin[n] < widgetW[i]) colWMin[n] = widgetW[i];
221 n = i / cols; // row
222 if (rowHMin[n] < widgetH[i]) rowHMin[n] = widgetH[i];
223 }
224 }
225
131 protected: 226 protected:
132 int rows, cols; // number of cells in grid 227 int cols, rows; // number of cells in grid
228
229 int colSizable = -2;// 0..cols-1 means this column is resizable
230 int rowSizable = -2;// -2 means not calculated yet, -1 means not resizable
231
232 int[] colWMin; // minimal column width
233 int[] rowHMin; // minimal row height
234 int[] colW; // column width (widest widget)
133 int[] rowH; // row height (highest widget in the row) 235 int[] rowH; // row height (highest widget in the row)
134 int[] colW; // column width (widest widget) 236
135 int[] rowY; // cumulative rowH[i-1] + border and padding 237 int[] colX; // cumulative colW[i-1] + padding (add x to get column's left x-coord)
136 int[] colX; // cumulative colW[i-1] + border and padding 238 int[] rowY; // cumulative rowH[i-1] + padding
239
137 IWidget[] subWidgets; // all widgets in the grid (by row): 240 IWidget[] subWidgets; // all widgets in the grid (by row):
138 /* SubWidget order: [ 0 1 ] 241 /* SubWidget order: [ 0 1 ]
139 * [ 2 3 ] */ 242 * [ 2 3 ] */
140 } 243 }