Mercurial > projects > mde
comparison mde/gui/widget/layout.d @ 45:0fd51d2c6c8a
Several changes to resising windows and layout widgets. This commit still has some bugs.
Moved the implementable widgets from mde.gui.widget.Widget to miscWidgets, leaving base widgets in Widget.
Rewrote some of GridLayoutWidget's implementation. Made many operations general to work for either columns or rows. Some optimisations were intended but ended up being removed due to problems.
Allowed layout's to resize from either direction (only with window resizes).
committer: Diggory Hardy <diggory.hardy@gmail.com>
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Thu, 22 May 2008 11:34:09 +0100 |
parents | 1530d9c04d4d |
children | 03fa79a48c48 |
comparison
equal
deleted
inserted
replaced
44:07bd1a09e161 | 45:0fd51d2c6c8a |
---|---|
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; | 20 import mde.gui.exception; |
21 | 21 |
22 import tango.io.Stdout; | |
23 debug { | 22 debug { |
24 import tango.util.log.Log : Log, Logger; | 23 import tango.util.log.Log : Log, Logger; |
25 private Logger logger; | 24 private Logger logger; |
26 static this () { | 25 static this () { |
27 logger = Log.getLogger ("mde.gui.widget.layout"); | 26 logger = Log.getLogger ("mde.gui.widget.layout"); |
45 super (wind, data); | 44 super (wind, data); |
46 | 45 |
47 rows = data[1]; | 46 rows = data[1]; |
48 cols = data[2]; | 47 cols = data[2]; |
49 if (data.length != 3 + rows * cols) throw new WidgetDataException; | 48 if (data.length != 3 + rows * cols) throw new WidgetDataException; |
50 /* data.length >= 3 so besides checking the length is correct, this tells us: | 49 /* data.length >= 4 so besides checking the length is correct, this tells us: |
51 * rows * cols >= 4 - 3 = 1 a free check! | 50 * rows * cols >= 4 - 3 = 1 a free check! |
52 * The only thing not checked is whether both rows and cols are negative, which would | 51 * The only thing not checked is whether both rows and cols are negative, which would |
53 * cause an exception when dynamic arrays are allocated later (and is unlikely). */ | 52 * cause an exception when dynamic arrays are allocated by genCachedConstructionData, which |
53 * is an acceptible method of failure (and is unlikely anyway). */ | |
54 | 54 |
55 // Get all sub-widgets | 55 // Get all sub-widgets |
56 subWidgets.length = rows*cols; | 56 subWidgets.length = rows*cols; |
57 foreach (i, ref subWidget; subWidgets) { | 57 foreach (i, ref subWidget; subWidgets) { |
58 subWidget = window.makeWidget (data[i+3]); | 58 subWidget = window.makeWidget (data[i+3]); |
72 * Note: if setSize gets called afterwards, it should have same dimensions and so not do | 72 * Note: if setSize gets called afterwards, it should have same dimensions and so not do |
73 * anything. */ | 73 * anything. */ |
74 | 74 |
75 int lenUsed = 0; | 75 int lenUsed = 0; |
76 if (data.length < rows + cols) { // data error; use defaults | 76 if (data.length < rows + cols) { // data error; use defaults |
77 colW = colWMin.dup; | 77 col.dupMin; |
78 rowH = rowHMin.dup; | 78 row.dupMin; |
79 } else { // sufficient data | 79 } else { // sufficient data |
80 lenUsed = rows+cols; | 80 lenUsed = rows+cols; |
81 colW = data[0..cols]; | 81 col.setCheck (data[0..cols]); |
82 rowH = data[cols..lenUsed]; | 82 row.setCheck (data[cols..lenUsed]); |
83 | 83 } |
84 // Check row sizes are valid: | 84 |
85 //NOTE: this could be made optional | 85 |
86 //NOTE: could also check non-resizable sizes are not too large | 86 // Generate cached mutable data |
87 foreach (i, ref w; colW) | 87 // Calculate column and row locations: |
88 if (w < colWMin[i]) w = colWMin[i]; | 88 w = col.genPositions; |
89 foreach (i, ref h; rowH) | 89 h = row.genPositions; |
90 if (h < rowHMin[i]) h = rowHMin[i]; | 90 |
91 } | 91 // Tell subwidgets their new sizes. Positions are given by a later call to setPosition. |
92 | 92 foreach (i,widget; subWidgets) |
93 genCachedMutableData; | 93 // Resizing direction is arbitrarily set to "high direction": |
94 w = colW[$-1] + colX[$-1]; | 94 widget.setSize (col.width[i % cols], row.width[i / cols], true, true); |
95 h = rowY[$-1] + rowH[$-1]; | |
96 | 95 |
97 return data[lenUsed..$]; | 96 return data[lenUsed..$]; |
98 } | 97 } |
99 | 98 |
100 int[] getCreationData () { | 99 int[] getCreationData () { |
111 int[] getMutableData () { | 110 int[] getMutableData () { |
112 int[] ret; | 111 int[] ret; |
113 foreach (widget; subWidgets) | 112 foreach (widget; subWidgets) |
114 ret ~= widget.getMutableData; | 113 ret ~= widget.getMutableData; |
115 | 114 |
116 ret ~= colW ~ rowH; | 115 ret ~= col.width ~ row.width; |
117 return ret; | 116 return ret; |
118 } | 117 } |
119 | 118 |
120 bool isWSizable () { | 119 bool isWSizable () { |
121 return (sizableCols.length != 0); | 120 return col.firstSizable >= 0; |
122 } | 121 } |
123 | |
124 bool isHSizable () { | 122 bool isHSizable () { |
125 return (sizableRows.length != 0); | 123 return row.firstSizable >= 0; |
126 } | 124 } |
127 | 125 |
128 /* Calculates the minimal size from all rows and columns of widgets. */ | 126 /* Calculates the minimal size from all rows and columns of widgets. */ |
129 void getMinimalSize (out int mw, out int mh) { | 127 void getMinimalSize (out int mw, out int mh) { |
130 mw = this.mw; | 128 mw = this.mw; |
131 mh = this.mh; | 129 mh = this.mh; |
132 } | 130 } |
133 | 131 |
134 void setSize (int nw, int nh) { | 132 void setSize (int nw, int nh, bool wHigh, bool hHigh) { |
135 // Step 1: calculate the row/column sizes. | 133 debug scope (failure) { |
136 w += adjustCellSizes (colW, colWMin, sizableCols, nw - w, true); | 134 char[128] tmp; |
137 h += adjustCellSizes (rowH, rowHMin, sizableRows, nh - h, true); | 135 logger.trace ("setSize failed: hHigh = " ~ (hHigh ? "true" : "false")); |
138 | 136 logger.trace (logger.format (tmp, "rows to resize: {}, {}", row.firstSizable, row.lastSizable)); |
139 // Step 2: calculate the row/column offsets (positions) and set the sub-widget's sizes. | 137 } |
140 genCachedMutableData; | 138 // Optimisation (could easily be called with same sizes if a parent layout widget is |
141 | 139 // resized, since many columns/rows may not be resized). |
142 // Step 3: position needs to be set | 140 if (nw == w && nh == h) return; |
143 // Currently this happens by specifying that setPosition should be run after setSize. | 141 |
142 // calculate the row/column sizes (and new positions) | |
143 if (wHigh) | |
144 w += col.adjustCellSizes (nw - w, col.lastSizable, -1); | |
145 else | |
146 w += col.adjustCellSizes (nw - w, col.firstSizable, 1); | |
147 if (hHigh) | |
148 h += row.adjustCellSizes (nh - h, row.lastSizable, -1); | |
149 else | |
150 h += row.adjustCellSizes (nh - h, row.firstSizable, 1); | |
151 | |
152 // set the sub-widget's sizes & positions | |
153 setSubWidgetSP (wHigh, hHigh); | |
144 } | 154 } |
145 | 155 |
146 void setPosition (int x, int y) { | 156 void setPosition (int x, int y) { |
147 this.x = x; | 157 this.x = x; |
148 this.y = y; | 158 this.y = y; |
149 | 159 |
150 foreach (i,widget; subWidgets) | 160 foreach (i,widget; subWidgets) |
151 widget.setPosition (x + colX[i % cols], y + rowY[i / cols]); | 161 widget.setPosition (x + col.pos[i % cols], y + row.pos[i / cols]); |
152 } | 162 } |
153 | 163 |
154 | 164 |
155 // Find the relevant widget. | 165 // Find the relevant widget. |
156 IWidget getWidget (int cx, int cy) { | 166 IWidget getWidget (int cx, int cy) { |
157 int lx = cx - x, ly = cy - y; // use coords relative to this widget | 167 debug scope (failure) |
158 | 168 logger.warn ("getWidget: failure"); |
159 // Find the column | 169 // Find row/column: |
160 int i = cols - 1; // starting from right... | 170 myDiff i = col.getCell (cx - x); |
161 while (lx < colX[i]) { // decrement while left of this column | 171 myDiff j = row.getCell (cy - y); |
162 debug assert (i > 0, "getWidget: left of first column"); // should be impossible | 172 if (i < 0 || j < 0) // on a space between widgets |
163 --i; | 173 return this; |
164 } // now (lx >= colX[i]) | 174 |
165 if (lx >= colX[i] + colW[i]) return this; // between columns | 175 // On a subwidget; recurse call: |
166 | 176 return subWidgets[i + j*cols].getWidget (cx, cy); |
167 // Find the row; | |
168 int j = rows - 1; | |
169 while (ly < rowY[j]) { | |
170 debug assert (j > 0, "getWidget: above first row"); // should be impossible | |
171 --j; | |
172 } | |
173 if (ly >= rowY[j] + rowH[j]) return this; | |
174 | |
175 // Now we know it's in widget (i,j)'s cell (but the widget may not take up the whole cell) | |
176 lx -= colX[i]; | |
177 ly -= rowY[j]; | |
178 IWidget widg = subWidgets[i + j*cols]; | |
179 widg.getCurrentSize (i,j); | |
180 if (lx < i && ly < j) | |
181 return widg.getWidget (cx, cy); | |
182 return this; // wasn't in cell | |
183 } | 177 } |
184 | 178 |
185 // Resizing columns & rows | 179 // Resizing columns & rows |
186 void clickEvent (ushort cx, ushort cy, ubyte b, bool state) { | 180 void clickEvent (ushort cx, ushort cy, ubyte b, bool state) { |
187 debug scope (failure) | 181 debug scope (failure) |
189 if (b == 1 && state == true) { | 183 if (b == 1 && state == true) { |
190 /* Note: Because of getWidget, this function is only called if the click is not on a | 184 /* Note: Because of getWidget, this function is only called if the click is not on a |
191 * sub-widget, so we know it's on some divisor (so at least one of resizeCol and | 185 * sub-widget, so we know it's on some divisor (so at least one of resizeCol and |
192 * resizeRow is non-negative). */ | 186 * resizeRow is non-negative). */ |
193 | 187 |
194 // Find the column | 188 // find col/row's resizeD & resizeU |
195 if (sizableCols.length != 0) { | 189 if (col.findResize (cx - x) && row.findResize (cy - y)) |
196 int l = cx - x; // use relative coords | 190 return; // unable to resize |
197 size_t i = cols - 1; // index, from right | |
198 while (l < colX[i]) { // decrement while left of this column | |
199 debug assert (i > 0, "clickEvent: left of first column"); | |
200 --i; | |
201 } // now (l >= colX[resizeCol]) | |
202 if (l < colX[i] + colW[i]) i = -1; // on a sub-widget | |
203 | |
204 // Set resizeColsL / resizeColsH | |
205 // Want to find j such that [0..j],[j..$] divide sizableCols about i: | |
206 size_t j = 0; | |
207 while (j < sizableCols.length && sizableCols[j] <= i) ++j; | |
208 | |
209 resizeColsL = sizableCols[0..j]; | |
210 resizeColsH = sizableCols[j..$]; | |
211 | |
212 // Cannot resize if either list is empty. resizeCallback checks the length of L, | |
213 // but to save it checking R too, we set L's length zero if R's is. | |
214 if (resizeColsH.length == 0) | |
215 resizeColsL = null; | |
216 } | |
217 | |
218 // Find the row | |
219 if (sizableRows.length != 0) { | |
220 int l = cy - y; | |
221 size_t i = rows - 1; | |
222 while (l < rowY[i]) { | |
223 debug assert (i > 0, "clickEvent: above first row"); | |
224 --i; | |
225 } | |
226 if (l < rowY[i] + rowH[i]) i = -1; | |
227 | |
228 size_t j = 0; | |
229 while (j < sizableRows.length && sizableRows[j] <= i) ++j; | |
230 | |
231 resizeRowsL = sizableRows[0..j]; | |
232 resizeRowsH = sizableRows[j..$]; | |
233 | |
234 if (resizeRowsH.length == 0) | |
235 resizeRowsL = null; | |
236 } | |
237 | |
238 if (resizeColsL is null && resizeRowsL is null) | |
239 return; // no resizing to do | |
240 | 191 |
241 dragX = cx; | 192 dragX = cx; |
242 dragY = cy; | 193 dragY = cy; |
243 | 194 |
244 window.gui.addClickCallback (&endCallback); | 195 window.gui.addClickCallback (&endCallback); |
256 private: | 207 private: |
257 //BEGIN Cache calculation functions | 208 //BEGIN Cache calculation functions |
258 /* Calculations which need to be run whenever a new sub-widget structure is set | 209 /* Calculations which need to be run whenever a new sub-widget structure is set |
259 * (i.e. to produce cached data calculated from construction data). */ | 210 * (i.e. to produce cached data calculated from construction data). */ |
260 void genCachedConstructionData () { | 211 void genCachedConstructionData () { |
212 col.spacing = row.spacing = window.renderer.layoutSpacing; | |
213 | |
261 // Calculate the minimal column and row sizes: | 214 // Calculate the minimal column and row sizes: |
262 colWMin = new int[cols]; // set length, making sure the arrays are initialised to zero | 215 // set length, making sure the arrays are initialised to zero: |
263 rowHMin = new int[rows]; | 216 col.minWidth = new int[cols]; |
217 row.minWidth = new int[rows]; | |
264 int ww, wh; // sub-widget minimal sizes | 218 int ww, wh; // sub-widget minimal sizes |
265 foreach (i,widget; subWidgets) { | 219 foreach (i,widget; subWidgets) { |
266 widget.getMinimalSize (ww, wh); | 220 widget.getMinimalSize (ww, wh); |
267 | 221 |
268 // Increase dimensions if current minimal size is larger: | 222 // Increase dimensions if current minimal size is larger: |
269 uint n = i % cols; // column | 223 myIt n = i % cols; // column |
270 if (colWMin[n] < ww) colWMin[n] = ww; | 224 if (col.minWidth[n] < ww) col.minWidth[n] = ww; |
271 n = i / cols; // row | 225 n = i / cols; // row |
272 if (rowHMin[n] < wh) rowHMin[n] = wh; | 226 if (row.minWidth[n] < wh) row.minWidth[n] = wh; |
273 } | 227 } |
274 | 228 |
275 | 229 |
276 // Calculate the overall minimal size, starting with the spacing: | 230 // Calculate the overall minimal size, starting with the spacing: |
277 mh = window.renderer.layoutSpacing; // use mh temporarily | 231 mh = window.renderer.layoutSpacing; // use mh temporarily |
278 mw = mh * (cols - 1); | 232 mw = mh * (cols - 1); |
279 mh *= (rows - 1); | 233 mh *= (rows - 1); |
280 | 234 |
281 foreach (x; colWMin) // add the column/row's dimensions | 235 foreach (x; col.minWidth) // add the column/row's dimensions |
282 mw += x; | 236 mw += x; |
283 foreach (x; rowHMin) | 237 foreach (x; row.minWidth) |
284 mh += x; | 238 mh += x; |
285 | 239 |
286 | 240 |
287 // Find which cols/rows are resizable: | 241 // Find which cols/rows are resizable: |
288 sizableCols = sizableRows = null; // reset; we're about to concatenate to them | 242 // reset: |
243 col.sizable = new bool[cols]; | |
244 row.sizable = new bool[rows]; | |
245 col.firstSizable = row.firstSizable = -1; | |
289 | 246 |
290 forCols: | 247 forCols: |
291 for (uint i = 0; i < cols; ++i) { // for each column | 248 for (myIt i = 0; i < cols; ++i) { // for each column |
292 for (uint j = 0; j < subWidgets.length; j += cols) // for each row | 249 for (myIt j = 0; j < subWidgets.length; j += cols) // for each row |
293 if (!subWidgets[i+j].isWSizable) // column not resizable | 250 if (!subWidgets[i+j].isWSizable) // column not resizable |
294 continue forCols; // continue the outer for loop | 251 continue forCols; // continue the outer for loop |
295 | 252 |
296 // column is resizable if we get to here | 253 // column is resizable if we get to here |
297 sizableCols ~= i; | 254 col.sizable[i] = true; |
255 if (col.firstSizable < 0) | |
256 col.firstSizable = i; | |
257 col.lastSizable = i; | |
298 } | 258 } |
299 | 259 |
300 forRows: | 260 forRows: |
301 for (uint i = 0; i < subWidgets.length; i += cols) { // for each row | 261 for (myIt i = 0; i < subWidgets.length; i += cols) { // for each row |
302 for (uint j = 0; j < cols; ++j) // for each column | 262 for (myIt j = 0; j < cols; ++j) // for each column |
303 if (!subWidgets[i+j].isHSizable) | 263 if (!subWidgets[i+j].isHSizable) |
304 continue forRows; | 264 continue forRows; |
305 | 265 |
306 sizableRows ~= i / cols; | 266 row.lastSizable = i / cols; |
307 } | 267 row.sizable[row.lastSizable] = true; |
308 } | 268 if (row.firstSizable < 0) |
309 | 269 row.firstSizable = row.lastSizable; |
310 /* Calculations which need to be run whenever resizing occurs (or deeper alterations) | 270 } |
311 * (i.e. to produce cached data calculated from construction and mutable data). */ | 271 } |
312 void genCachedMutableData () { | 272 |
313 // Calculate column and row locations: | 273 // set sub-widgets size & position (done after resizing widget or rows/columns) |
314 colX.length = cols; | 274 void setSubWidgetSP (bool wH, bool hH) { |
315 rowY.length = rows; | 275 for (myIt i = 0; i < cols; ++i) |
316 int spacing = window.renderer.layoutSpacing; | 276 for (myIt j = 0; j < rows; ++j) |
317 | 277 { |
318 int cum = 0; | 278 IWidget widget = subWidgets[i + cols*j]; |
319 foreach (i, x; rowH) { | 279 widget.setSize (col.width[i], row.width[j], wH, hH); |
320 rowY[i] = cum; | 280 widget.setPosition (x + col.pos[i], y + row.pos[j]); |
321 cum += x + spacing; | 281 } |
322 } | |
323 | |
324 cum = 0; | |
325 foreach (i, x; colW) { | |
326 colX[i] = cum; | |
327 cum += x + spacing; | |
328 } | |
329 | |
330 // Tell subwidgets their new sizes: | |
331 foreach (i,widget; subWidgets) | |
332 widget.setSize (colW[i % cols], rowH[i / cols]); | |
333 } | 282 } |
334 //END Cache calculation functions | 283 //END Cache calculation functions |
335 | 284 |
336 /* Adjust the total size of rows/columns (including spacing) by diff. | |
337 * | |
338 * Params: | |
339 * cellD = current sizes; is adjusted by the function to new sizes | |
340 * cellDMin = minimal sizes (see colWMin/rowHMin) | |
341 * sizableCells= List of indexes in cellD for cells which are resizable | |
342 * diff = amount to increase/decrease the total size | |
343 * startHigh = if true, start resizing from the tail end of cellD, instead of the beginning | |
344 * | |
345 * Returns: | |
346 * The amount adjusted. This may be larger than diff, since cellD is clamped by cellDMin. | |
347 */ | |
348 int adjustCellSizes (ref int[] cellD, ref int[] cellDMin, ref size_t[] sizableCells, int diff, bool startHigh) | |
349 in {// Could occur if adjust isn't called first, but this would be a code error: | |
350 assert (cellD !is null, "adjustCellSizes: cellD is null"); | |
351 } body { | |
352 // Cannot resize if no cells are sizable: | |
353 if (sizableCells.length == 0) return 0; | |
354 | |
355 size_t si = (startHigh ? sizableCells.length-1 : 0); // starting index of sizableCells | |
356 | |
357 // FIXME: could do with an overhaul | |
358 if (diff > 0) { // increase size of first resizable cell | |
359 cellD[sizableCells[si]] += diff; | |
360 return diff; | |
361 } | |
362 else if (diff < 0) { // decrease | |
363 size_t sc = (startHigh ? -1 : 1); // amount to iterate | |
364 size_t ci; // index in cellD | |
365 int rd = diff; // running diff | |
366 while (true) { | |
367 ci = sizableCells[si]; | |
368 | |
369 cellD[ci] += rd; // decrease this cell's size (but may be too much) | |
370 rd = cellD[ci] - cellDMin[ci]; | |
371 if (rd >= 0) // OK; we're done | |
372 return diff;// we hit the mark exactly | |
373 | |
374 // else we decreased it too much! | |
375 cellD[ci] = cellDMin[ci]; | |
376 // rd is remainder to decrease by | |
377 | |
378 si += sc; // iterate | |
379 if (si < 0 || si >= sizableCells.length) // run out of next cells | |
380 return diff - rd; // still had rd left to decrease | |
381 } | |
382 } | |
383 else // no adjustment needed | |
384 return 0; | |
385 } | |
386 | 285 |
387 //BEGIN Col/row resizing | 286 //BEGIN Col/row resizing |
388 void resizeCallback (ushort cx, ushort cy) { | 287 void resizeCallback (ushort cx, ushort cy) { |
389 int move; // NOTE: all resizing is relative, unlike in Window | 288 col.resize (cx - dragX); |
390 | 289 row.resize (cy - dragY); |
391 if (resizeColsL.length != 0) { | 290 |
392 move = cx - dragX; | 291 // NOTE: all adjustments are relative; might be better if they were absolute? |
393 | 292 dragX = cx; |
394 // do shrinking first (in case we hit the minimum) | 293 dragY = cy; |
395 // Note: we rely on x[a..b] pointing to the same memory as x | 294 |
396 if (move < 0) { | 295 // NOTE: Resizing direction is set to "high direction" which isn't always going to be |
397 move = -adjustCellSizes (colW, colWMin, resizeColsL, move, false); | 296 // correct. A more accurate but more complex approach might be to get |
398 adjustCellSizes (colW, colWMin, resizeColsH, move, true); | 297 // adjustCellSizes to do the work. |
399 } else { | 298 setSubWidgetSP (true, true); |
400 move = -adjustCellSizes (colW, colWMin, resizeColsH, -move, true); | |
401 adjustCellSizes (colW, colWMin, resizeColsL, move, false); | |
402 } | |
403 | |
404 dragX = cx; | |
405 } | |
406 | |
407 if (resizeRowsL.length != 0) { | |
408 move = cy - dragY; | |
409 | |
410 // do shrinking first (in case we hit the minimum) | |
411 // Note: we rely on x[a..b] pointing to the same memory as x | |
412 if (move < 0) { | |
413 move = -adjustCellSizes (rowH, rowHMin, resizeRowsL, move, false); | |
414 adjustCellSizes (rowH, rowHMin, resizeRowsH, move, true); | |
415 } else { | |
416 move = -adjustCellSizes (rowH, rowHMin, resizeRowsH, -move, true); | |
417 adjustCellSizes (rowH, rowHMin, resizeRowsL, move, false); | |
418 } | |
419 | |
420 dragY = cy; | |
421 } | |
422 | |
423 // NOTE: this might be able to be made more efficient, but basically this all needs to happen: | |
424 genCachedMutableData; | |
425 setPosition (x,y); | |
426 window.requestRedraw; | 299 window.requestRedraw; |
427 } | 300 } |
428 bool endCallback (ushort cx, ushort cy, ubyte b, bool state) { | 301 bool endCallback (ushort cx, ushort cy, ubyte b, bool state) { |
429 if (b == 1 && state == false) { | 302 if (b == 1 && state == false) { |
430 window.gui.removeCallbacks (cast(void*) this); | 303 window.gui.removeCallbacks (cast(void*) this); |
431 // Remove unwanted data (although it shouldn't free any memory): | 304 return true; // we've handled the up-click |
432 resizeColsL = resizeColsH = resizeRowsL = resizeRowsH = null; | 305 } |
433 return true; // we've handled the up-click | 306 return false; // we haven't handled it |
434 } | |
435 return false; // we haven't handled it | |
436 } | 307 } |
437 | 308 |
438 protected: | 309 protected: |
439 // Data for resizing cols/rows: | 310 // Data for resizing cols/rows: |
440 int dragX, dragY; // coords where drag starts | 311 int dragX, dragY; // coords where drag starts |
441 // Lists of columns/rows with lower/higher index than the resize position | |
442 size_t[] resizeColsL, resizeColsH, resizeRowsL, resizeRowsH; | |
443 //END Col/row resizing | 312 //END Col/row resizing |
444 | 313 |
445 | 314 |
446 //BEGIN Construction (i.e. non-mutable) data | 315 myIt cols, rows; // number of cells in grid |
447 int cols, rows; // number of cells in grid | |
448 | 316 |
449 /* All widgets in the grid, by row. Order: [ 0 1 ] | 317 /* All widgets in the grid, by row. Order: [ 0 1 ] |
450 * [ 2 3 ] */ | 318 * [ 2 3 ] */ |
451 IWidget[] subWidgets; | 319 IWidget[] subWidgets; |
452 | 320 |
453 // Cached data calculated from construction data: | 321 /* Widths, positions, etc., either of columns or of rows |
454 // Minimal column width / row height: | 322 * |
455 int[] colWMin, rowHMin; | 323 * The purpose of this struct is mostly to unify functionality which must work the same on both |
456 int mw, mh; // minimal dimensions | 324 * horizontal and vertical cell placement. |
457 | 325 * |
458 // All resizable cols/rows, in order: | 326 * Most notation corresponds to horizontal layout (columns), simply for easy of naming. */ |
459 size_t[] sizableCols, sizableRows; | 327 struct CellDimensions { |
460 //END Construction data | 328 int[] pos, // relative position (cumulative width[i-1] plus spacing) |
461 | 329 width, // current widths |
462 | 330 minWidth; // minimal widths (set by genCachedConstructionData) |
463 //BEGIN Mutable data | 331 bool[] sizable; // true if col is resizable (set by genCachedConstructionData) |
464 int[] colW, rowH; // column width / row height (largest size in col/row) | 332 myDiff firstSizable, // first col which is resizable, negative if none |
465 | 333 lastSizable; // as above, but last (set by genCachedConstructionData) |
466 // Cached data calculated from construction and mutable data: | 334 myDiff resizeD, // resize down from this index (<0 if not resizing) |
467 int[] colX, rowY; // cumulative colW[i-1] + padding (add x to get column's left x-coord) | 335 resizeU; // and up from this index |
468 //END Mutable data | 336 int spacing; // used by genPositions (which cannot access the layout class's data) |
337 | |
338 void dupMin () { | |
339 width = minWidth.dup; | |
340 } | |
341 void setCheck (int[] data) { | |
342 // Set to provided data: | |
343 width = data; | |
344 // And check sizes are valid: | |
345 foreach (i, m; minWidth) | |
346 // if fixed width or width is less than minimum: | |
347 if (!sizable[i] || width[i] < m) | |
348 width[i] = m; | |
349 } | |
350 | |
351 // Generate position infomation and return total width (i.e. widget width/height) | |
352 int genPositions () { | |
353 pos.length = minWidth.length; | |
354 | |
355 int x = 0; | |
356 foreach (i, w; width) { | |
357 pos[i] = x; | |
358 x += w + spacing; | |
359 } | |
360 return x - spacing; | |
361 } | |
362 | |
363 // Get the row/column a click occured in | |
364 // Returns -i if in space to left of col i, or i if on col i | |
365 myDiff getCell (int l) { | |
366 myDiff i = minWidth.length - 1; // starting from right... | |
367 while (l < pos[i]) { // decrement while left of this column | |
368 debug assert (i > 0, "getCell: l < pos[0] (code error)"); | |
369 --i; | |
370 } // now (l >= pos[i]) | |
371 if (l >= pos[i] + width[i]) // between columns | |
372 return -i - 1; // note: i might be 0 so cannot just return -i | |
373 return i; | |
374 } | |
375 | |
376 // Calculate resizeU/resizeD, and return true if unable to resize. | |
377 bool findResize (int l) { | |
378 resizeU = -getCell (l); // potential start for upward-resizes | |
379 if (resizeU <= 0) return true; // not on a space between cells | |
380 resizeD = resizeU - 1; // potential start for downward-resizes | |
381 | |
382 while (!sizable[resizeU]) { // find first actually resizable column (upwards) | |
383 ++resizeU; | |
384 if (resizeU >= minWidth.length) { // cannot resize | |
385 resizeU = -1; | |
386 return true; | |
387 } | |
388 } | |
389 | |
390 while (!sizable[resizeD]) { // find first actually resizable column (downwards) | |
391 --resizeD; | |
392 if (resizeD < 0) { // cannot resize | |
393 resizeU = -1; // resizeU is tested to check whether resizes are possible | |
394 return true; | |
395 } | |
396 } | |
397 | |
398 return false; // can resize | |
399 } | |
400 | |
401 /* Adjust the total size of rows/columns (including spacing) by diff. | |
402 * | |
403 * Params: | |
404 * diff = amount to increase/decrease the total size | |
405 * start= index for col/row to start resizing on | |
406 * incr = direction to resize in (added to index each step). Must be either -1 or +1. | |
407 * | |
408 * Returns: | |
409 * The amount adjusted. This may be larger than diff, since cellD is clamped by cellDMin. | |
410 */ | |
411 int adjustCellSizes (int diff, myDiff start, myDiff incr) | |
412 in {// Could occur if adjust isn't called first, but this would be a code error: | |
413 char[128] tmp; | |
414 logger.trace (logger.format (tmp, "start is {}", start)); | |
415 assert (width !is null, "adjustCellSizes: width is null"); | |
416 assert (start >= 0 && start < minWidth.length, "adjustCellSizes: invalid start"); | |
417 assert (incr == 1 || incr == -1, "adjustCellSizes: invalid incr"); | |
418 } body { | |
419 debug scope(failure) | |
420 logger.trace ("adjustCellSizes: failure"); | |
421 myDiff i = start; | |
422 if (diff > 0) { // increase size of first resizable cell | |
423 width[i] += diff; | |
424 } | |
425 else if (diff < 0) { // decrease | |
426 int rd = diff; // running diff | |
427 aCSwhile: | |
428 while (true) { | |
429 width[i] += rd; // decrease this cell's size (but may be too much) | |
430 rd = width[i] - minWidth[i]; | |
431 if (rd >= 0) // OK; we're done | |
432 break; // we hit the mark exactly: diff is correct | |
433 | |
434 // else we decreased it too much! | |
435 width[i] = minWidth[i]; | |
436 // rd is remainder to decrease by | |
437 | |
438 bool it = true; // iterate (force first time) | |
439 while (it) { | |
440 i += incr; | |
441 if (i < 0 || i >= minWidth.length) { // run out of next cells | |
442 diff -= rd; // still had rd left to decrease | |
443 break aCSwhile; // exception: Array index out of bounds | |
444 } | |
445 it = !sizable[i]; // iterate again if row/col isn't resizable | |
446 } | |
447 } | |
448 } | |
449 // else no adjustment needed (diff == 0) | |
450 | |
451 genPositions; | |
452 return diff; | |
453 } | |
454 | |
455 void resize (int diff) { | |
456 if (resizeU <= 0) return; | |
457 | |
458 // do shrinking first (in case we hit the minimum) | |
459 if (diff >= 0) { | |
460 diff = -adjustCellSizes (-diff, resizeU, 1); | |
461 adjustCellSizes (diff, resizeD, -1); | |
462 } else { | |
463 diff = -adjustCellSizes (diff, resizeD, -1); | |
464 adjustCellSizes (diff, resizeU, 1); | |
465 } | |
466 } | |
467 } | |
468 CellDimensions col, row; | |
469 | |
470 // Index types. Note that in some cases they need to hold negative values. | |
471 alias size_t myIt; | |
472 alias ptrdiff_t myDiff; | |
469 } | 473 } |