comparison mde/gui/widget/layout.d @ 91:4d5d53e4f881

Shared alignment for dynamic content lists - finally implemented! Lots of smaller changes too. Some debugging improvements. When multiple .mtt files are read for merging, files with invalid headers are ignored and no error is thrown so long as at least one file os valid.
author Diggory Hardy <diggory.hardy@gmail.com>
date Thu, 16 Oct 2008 17:43:48 +0100
parents b525ff28774b
children 085f2ca31914
comparison
equal deleted inserted replaced
90:b525ff28774b 91:4d5d53e4f881
20 import mde.gui.exception; 20 import mde.gui.exception;
21 21
22 import mde.gui.widget.TextWidget; 22 import mde.gui.widget.TextWidget;
23 import mde.gui.content.options; 23 import mde.gui.content.options;
24 import mde.gui.content.Content; 24 import mde.gui.content.Content;
25
26 import tango.util.container.HashMap;
25 27
26 debug { 28 debug {
27 import tango.util.log.Log : Log, Logger; 29 import tango.util.log.Log : Log, Logger;
28 private Logger logger; 30 private Logger logger;
29 static this () { 31 static this () {
44 class GridLayoutWidget : GridWidget 46 class GridLayoutWidget : GridWidget
45 { 47 {
46 /** Constructor for a grid layout widget. 48 /** Constructor for a grid layout widget.
47 * 49 *
48 * Widget uses the initialisation data: 50 * Widget uses the initialisation data:
49 * [widgetID, r, c, w11, w12, ..., w1c, ..., wr1, ..., wrc] 51 * ---
50 * where r and c are the number of rows and columns, and wij is the ID (from parent Window's 52 * ints = [widget_type, align_flags, rows, cols]
53 * // or with column widths and row heights:
54 * ints = [widget_type, align_flags, rows, cols, col1width, ..., colCwidth, row1height, ..., rowRheight]
55 * strings = [w11, w12, ..., w1C, ..., wR1, ..., wRC]
56 * ---
57 * where R and C are the number of rows and columns, and wij is the ID (from parent Window's
51 * list) for the widget in row i and column j. The number of parameters must be r*c + 3. 58 * list) for the widget in row i and column j. The number of parameters must be r*c + 3.
52 * 59 *
53 * The content parameter is passed on to all children accepting an IContent. */ 60 * The content parameter is passed on to all children accepting an IContent. */
54 this (IWidgetManager mgr, WidgetData data, IContent content) { 61 this (IWidgetManager mgr, widgetID id, WidgetData data, IContent content) {
55 // Get grid size and check data 62 // Get grid size and check data
56 // Check sufficient data for rows, cols, and possibly row/col widths. 63 // Check sufficient data for type, align-flags, rows, cols, and possibly row/col widths.
57 if (data.ints.length < 3) throw new WidgetDataException; 64 if (data.ints.length < 4) throw new WidgetDataException (this);
58 65
59 rows = data.ints[1]; 66 rows = data.ints[2];
60 cols = data.ints[2]; 67 cols = data.ints[3];
61 // Check: at least one sub-widget, ints length == 3 or also contains row & col widths, 68 // Check: at least one sub-widget, ints length == 3 or also contains row & col widths,
62 // strings' length is correct: 69 // strings' length is correct:
63 if (rows < 1 || cols < 1 || 70 if (rows < 1 || cols < 1 ||
64 (data.ints.length != 3 && data.ints.length != 3 + rows + cols) || 71 (data.ints.length != 4 && data.ints.length != 4 + rows + cols) ||
65 data.strings.length != rows * cols) 72 data.strings.length != rows * cols)
66 throw new WidgetDataException; 73 throw new WidgetDataException (this);
67 this.data = data; 74 this.data = data;
68 75
69 // Get all sub-widgets 76 // Get all sub-widgets
70 subWidgets.length = rows*cols; 77 subWidgets.length = rows*cols;
71 foreach (i, ref subWidget; subWidgets) { 78 foreach (i, ref subWidget; subWidgets) {
72 subWidget = mgr.makeWidget (data.strings[i], content); 79 subWidget = mgr.makeWidget (data.strings[i], content);
73 } 80 }
74 81
75 super (mgr, data); 82 if (data.ints.length == 4 + rows + cols)
76 83 initWidths = cast(wdim[]) data.ints[4..$];
77 if (data.ints.length == 3 + rows + cols) { 84
78 col.setWidths (cast(wdim[]) data.ints[3..cols+3]); 85 super (mgr, id, data);
79 row.setWidths (cast(wdim[]) data.ints[cols+3..$]);
80 } else {
81 col.setWidths;
82 row.setWidths;
83 }
84 adjustCache;
85 } 86 }
86 87
87 // Save column/row sizes. Currently always do so. 88 // Save column/row sizes. Currently always do so.
88 bool saveChanges (widgetID id) { 89 bool saveChanges (widgetID id) {
89 with (data) { 90 with (data) {
90 foreach (i, widget; subWidgets) // recurse on subwidgets 91 foreach (i, widget; subWidgets) // recurse on subwidgets
91 widget.saveChanges (strings[i]); 92 widget.saveChanges (strings[i]);
92 93
93 ints = ints[0..3] ~ cast(int[])col.width ~ cast(int[])row.width; 94 ints = ints[0..4] ~ cast(int[])col.width ~ cast(int[])row.width;
94 } 95 }
95 mgr.setData (id, data); 96 mgr.setData (id, data);
96 return true; 97 return true;
97 } 98 }
98 protected: 99 protected:
103 /************************************************************************************************* 104 /*************************************************************************************************
104 * Trial layout of sub-widgets of one type only. 105 * Trial layout of sub-widgets of one type only.
105 *************************************************************************************************/ 106 *************************************************************************************************/
106 class TrialContentLayoutWidget : GridWidget 107 class TrialContentLayoutWidget : GridWidget
107 { 108 {
108 this (IWidgetManager mgr, WidgetData data) { 109 this (IWidgetManager mgr, widgetID id, WidgetData data) {
109 debug scope (failure) 110 debug scope (failure)
110 logger.warn ("TrialContentLayoutWidget: failure"); 111 logger.warn ("TrialContentLayoutWidget: failure");
111 WDCheck (data, 1, 1); 112 WDCheck (data, 2, 1);
112 113
113 OptionList optsList = OptionList.trial(); 114 OptionList optsList = OptionList.trial();
114 rows = optsList.list.length; 115 rows = optsList.list.length;
115 cols = 1; 116 cols = 1;
116 117
117 // Get all sub-widgets 118 // Get all sub-widgets
118 subWidgets.length = rows*cols; 119 subWidgets.length = rows*cols;
119 foreach (i, c; optsList.list) { 120 foreach (i, c; optsList.list) {
120 subWidgets[i] = mgr.makeWidget (data.strings[0], c); 121 subWidgets[i] = mgr.makeWidget (data.strings[0], c);
121 } 122 }
122 super (mgr, data); 123 super (mgr, id, data);
123
124 // Set col/row widths to minimals.
125 col.setWidths;
126 row.setWidths;
127 adjustCache;
128 } 124 }
129 125
130 private: 126 private:
131 OptionList optsList; 127 OptionList optsList;
132 } 128 }
149 * 145 *
150 * Deriving classes should check data lengths, and set rows, cols, and the subWidgets array, 146 * Deriving classes should check data lengths, and set rows, cols, and the subWidgets array,
151 * before calling this super constructor. (If it's necessary to call super(...) first, 147 * before calling this super constructor. (If it's necessary to call super(...) first,
152 * the call to genCachedConstructionData can be moved to the derived this() methods.) 148 * the call to genCachedConstructionData can be moved to the derived this() methods.)
153 * 149 *
154 * Derived constructors should call setWidths on col and row, and then call 150 * Derived constructors may also set initWidths to the array of column widths followed by
155 * adjustCache, after calling this. */ 151 * row heights used to initially set the row/column dimensions. */
156 protected this (IWidgetManager mgr, WidgetData data) { 152 protected this (IWidgetManager mgr, widgetID id, WidgetData data) {
157 super (mgr, data); 153 super (mgr, id, data);
158 154
159 // Create cell aligners with appropriate col/row adjustment function 155 // Create cell aligners with appropriate col/row adjustment function
160 col = (new AlignColumns (cols)).addSetCallback (&setColWidth); 156 if (data.ints[1] & 1)
161 row = (new AlignColumns (rows)).addSetCallback (&setRowHeight); 157 col = AlignColumns.getInstance (id, cols);
158 else
159 col = (new AlignColumns (cols));
160 col.addSetCallback (&setColWidth);
161 if (data.ints[1] & 2)
162 row = AlignColumns.getInstance (id~"R", rows); // id must be unique to that for cols!
163 else
164 row = (new AlignColumns (rows));
165 row.addSetCallback (&setRowHeight);
162 166
163 // Calculate cached construction data 167 // Calculate cached construction data
164 genCachedConstructionData; 168 genCachedConstructionData;
165 } 169 }
166 170
167 /** Generates cached mutable data. 171 /** Responsible for calculating the minimal size and initializing some stuff.
168 * 172 *
169 * Should be called by adjust() after calling setWidths. */ 173 * As such, this must be the first function called after this(). */
170 void adjustCache () { 174 wdim minWidth () {
171 // Generate cached mutable data 175 if (!alignInit) { // assumes col & row.width are initialized simultaneously
172 // Calculate column and row locations: 176 alignInit = true;
173 w = col.genPositions; 177 if (initWidths) {
174 h = row.genPositions; 178 debug assert (initWidths.length == cols + rows, "initWidths provided but has bad length");
175 179 col.setWidths (initWidths[0..cols]);
176 // Tell subwidgets their new sizes. Positions are given by a later call to setPosition. 180 row.setWidths (initWidths[cols..$]);
177 foreach (i,widget; subWidgets) { 181 initWidths = null; // free
178 // Resizing direction is arbitrarily set to negative: 182 } else {
179 widget.setWidth (col.width[i % cols], -1); 183 col.setWidths;
180 widget.setHeight (row.width[i / cols], -1); 184 row.setWidths;
181 } 185 }
186
187 mw = col.mw;
188 mh = row.mw;
189 w = col.w;
190 h = row.w;
191
192 // Tell subwidgets their new sizes. Positions are given by a later call to setPosition.
193 foreach (i,widget; subWidgets) {
194 // Resizing direction is arbitrarily set to negative:
195 widget.setWidth (col.width[i % cols], -1);
196 widget.setHeight (row.width[i / cols], -1);
197 }
198 }
199 return mw;
182 } 200 }
183 //END Creation & saving 201 //END Creation & saving
184 202
185 //BEGIN Size & position 203 //BEGIN Size & position
186 bool isWSizable () { 204 bool isWSizable () {
189 bool isHSizable () { 207 bool isHSizable () {
190 return row.firstSizable >= 0; 208 return row.firstSizable >= 0;
191 } 209 }
192 210
193 void setWidth (wdim nw, int dir) { 211 void setWidth (wdim nw, int dir) {
194 if (nw == w) return; 212 w = col.resizeWidth (nw, dir);
195
196 w += col.adjustCellSizes (nw - w, (dir == -1 ? col.lastSizable : col.firstSizable), dir);
197
198 // Note: setPosition must be called after! 213 // Note: setPosition must be called after!
199 } 214 }
200 void setHeight (wdim nh, int dir) { 215 void setHeight (wdim nh, int dir) {
201 if (nh == h) return; 216 h = row.resizeWidth (nh, dir);
202
203 h += row.adjustCellSizes (nh - h, (dir == -1 ? row.lastSizable : row.firstSizable), dir);
204
205 // Note: setPosition must be called after! 217 // Note: setPosition must be called after!
206 } 218 }
207 219
208 void setPosition (wdim x, wdim y) { 220 void setPosition (wdim x, wdim y) {
209 this.x = x; 221 this.x = x;
210 this.y = y; 222 this.y = y;
211 223
224 debug assert (col.pos && row.pos, "setPosition: col/row.pos not set (code error)");
212 foreach (i,widget; subWidgets) 225 foreach (i,widget; subWidgets)
213 widget.setPosition (x + col.pos[i % cols], y + row.pos[i / cols]); 226 widget.setPosition (x + col.pos[i % cols], y + row.pos[i / cols]);
214 } 227 }
215 //END Size & position 228 //END Size & position
216 229
217 230
218 // Find the relevant widget. 231 // Find the relevant widget.
219 IChildWidget getWidget (wdim cx, wdim cy) { 232 IChildWidget getWidget (wdim cx, wdim cy) {
220 debug scope (failure) 233 debug scope (failure)
221 logger.warn ("getWidget: failure"); 234 logger.warn ("getWidget: failure; values: click, pos, width - {}, {}, {} - {}, {}, {}", cx, x, w, cy, y, h);
235 debug assert (cx >= x && cx < x + w && cy >= y && cy < y + h, "getWidget: not on widget (code error)");
236
222 // Find row/column: 237 // Find row/column:
223 myDiff i = col.getCell (cx - x); 238 myDiff i = col.getCell (cx - x);
224 myDiff j = row.getCell (cy - y); 239 myDiff j = row.getCell (cy - y);
225 if (i < 0 || j < 0) // on a space between widgets 240 if (i < 0 || j < 0) // on a space between widgets
226 return this; 241 return this;
237 /* Note: Because of getWidget, this function is only called if the click is not on a 252 /* Note: Because of getWidget, this function is only called if the click is not on a
238 * sub-widget, so we know it's on some divisor (so at least one of resizeCol and 253 * sub-widget, so we know it's on some divisor (so at least one of resizeCol and
239 * resizeRow is non-negative). */ 254 * resizeRow is non-negative). */
240 255
241 // find col/row's resizeD & resizeU 256 // find col/row's resizeD & resizeU
242 if (col.findResize (cx - x) && row.findResize (cy - y)) 257 if (col.findResizeCols (cx - x) && row.findResizeCols (cy - y))
243 return; // unable to resize 258 return; // unable to resize
244 259
245 dragX = cx; 260 dragX = cx;
246 dragY = cy; 261 dragY = cy;
247 262
261 //BEGIN Cache calculation functions 276 //BEGIN Cache calculation functions
262 /* Calculations which need to be run whenever a new sub-widget structure is set 277 /* Calculations which need to be run whenever a new sub-widget structure is set
263 * (i.e. to produce cached data calculated from construction data). 278 * (i.e. to produce cached data calculated from construction data).
264 * Also need to be re-run if the renderer changes. 279 * Also need to be re-run if the renderer changes.
265 * 280 *
266 * rows, cols and subWidgets must be set before calling. */ 281 * rows, cols and subWidgets must be set before calling. Part of the set-up for AlignColumns
282 * (col and row). */
267 void genCachedConstructionData () { 283 void genCachedConstructionData () {
268 // Will only change if renderer changes: 284 // Will only change if renderer changes:
269 col.spacing = row.spacing = mgr.renderer.layoutSpacing; 285 col.spacing = row.spacing = mgr.renderer.layoutSpacing;
270 286
271 // Calculate the minimal column and row sizes: 287 // Calculate the minimal column and row sizes:
272 // set length, making sure the arrays are initialised to zero: 288 // AlignColumns (row, col) takes care of initializing minWidth.
273 col.minWidth = new wdim[cols];
274 row.minWidth = new wdim[rows];
275 foreach (i,widget; subWidgets) { 289 foreach (i,widget; subWidgets) {
276 // Increase dimensions if current minimal size is larger: 290 // Increase dimensions if current minimal size is larger:
277 myIt n = i % cols; // column 291 myIt n = i % cols; // column
278 wdim md = widget.minWidth; 292 wdim md = widget.minWidth;
279 if (col.minWidth[n] < md) col.minWidth[n] = md; 293 if (col.minWidth[n] < md) col.minWidth[n] = md;
280 n = i / cols; // row 294 n = i / cols; // row
281 md = widget.minHeight; 295 md = widget.minHeight;
282 if (row.minWidth[n] < md) row.minWidth[n] = md; 296 if (row.minWidth[n] < md) row.minWidth[n] = md;
283 } 297 }
284 298
285
286 // Calculate the overall minimal size, starting with the spacing:
287 mh = mgr.renderer.layoutSpacing; // use mh temporarily
288 mw = mh * cast(wdim)(cols - 1);
289 mh *= cast(wdim)(rows - 1);
290
291 foreach (x; col.minWidth) // add the column/row's dimensions
292 mw += x;
293 foreach (x; row.minWidth)
294 mh += x;
295
296
297 // Find which cols/rows are resizable: 299 // Find which cols/rows are resizable:
298 // reset: 300 // AlignColumns initializes sizable, and sets first and last sizables.
299 col.sizable = new bool[cols];
300 row.sizable = new bool[rows];
301 col.firstSizable = row.firstSizable = -1;
302
303 forCols: 301 forCols:
304 for (myIt i = 0; i < cols; ++i) { // for each column 302 for (myIt i = 0; i < cols; ++i) { // for each column
305 for (myIt j = 0; j < subWidgets.length; j += cols) // for each row 303 for (myIt j = 0; j < subWidgets.length; j += cols) // for each row
306 if (!subWidgets[i+j].isWSizable) // column not resizable 304 if (!subWidgets[i+j].isWSizable) // column not resizable
307 continue forCols; // continue the outer for loop 305 continue forCols; // continue the outer for loop
308 306
309 // column is resizable if we get to here 307 // column is resizable if we get to here
310 col.sizable[i] = true; 308 col.sizable[i] = true;
311 if (col.firstSizable < 0)
312 col.firstSizable = i;
313 col.lastSizable = i;
314 } 309 }
315 310
316 forRows: 311 forRows:
317 for (myIt i = 0; i < subWidgets.length; i += cols) { // for each row 312 for (myIt i = 0; i < subWidgets.length; i += cols) { // for each row
318 for (myIt j = 0; j < cols; ++j) // for each column 313 for (myIt j = 0; j < cols; ++j) // for each column
319 if (!subWidgets[i+j].isHSizable) 314 if (!subWidgets[i+j].isHSizable)
320 continue forRows; 315 continue forRows;
321 316
322 row.lastSizable = i / cols; 317 row.sizable[i / cols] = true;
323 row.sizable[row.lastSizable] = true;
324 if (row.firstSizable < 0)
325 row.firstSizable = row.lastSizable;
326 } 318 }
327 } 319 }
328 //END Cache calculation functions 320 //END Cache calculation functions
329 321
330 322
340 } 332 }
341 333
342 334
343 //BEGIN Col/row resizing callback 335 //BEGIN Col/row resizing callback
344 void resizeCallback (wdim cx, wdim cy) { 336 void resizeCallback (wdim cx, wdim cy) {
345 col.resize (cx - dragX); 337 col.resizeCols (cx - dragX);
346 row.resize (cy - dragY); 338 row.resizeCols (cy - dragY);
347 339
348 // NOTE: all adjustments are relative; might be better if they were absolute? 340 // NOTE: all adjustments are relative; might be better if they were absolute?
349 dragX = cx; 341 dragX = cx;
350 dragY = cy; 342 dragY = cy;
351 343
365 protected: 357 protected:
366 // Data for resizing cols/rows: 358 // Data for resizing cols/rows:
367 wdim dragX, dragY; // coords where drag starts 359 wdim dragX, dragY; // coords where drag starts
368 //END Col/row resizing callback 360 //END Col/row resizing callback
369 361
370
371 myIt cols, rows; // number of cells in grid 362 myIt cols, rows; // number of cells in grid
363 wdim[] initWidths; // see this / setInitialSize
364 bool alignInit; // have AlignColumns instances been initialized?
372 365
373 /* All widgets in the grid, by row. Order: [ 0 1 ] 366 /* All widgets in the grid, by row. Order: [ 0 1 ]
374 * [ 2 3 ] */ 367 * [ 2 3 ] */
375 IChildWidget[] subWidgets; 368 IChildWidget[] subWidgets;
376 369
377 AlignColumns col, row; 370 AlignColumns col, row;
378 } 371 }
379 372
380 /// Position information on top of widths. 373
381 //FIXME - merge classes back together? 374 /**************************************************************************************************
382 class AlignColumns : AlignWidths 375 * Alignment device
376 *
377 * E.g. can control widths of columns within a grid, and provide sensible resizing, respecting the
378 * minimal width required by each cell in a column. Is not restricted to horizontal widths, but to
379 * ease descriptions, a horizontal context (column widths) is assumed.
380 *
381 * Cells should be of type IChildWidget.
382 *
383 * Cells are not directly interacted with, but minimal widths for each column are passed, and
384 * callback functions are used to adjust the width of any column.
385 *************************************************************************************************/
386 class AlignColumns
383 { 387 {
384 /// See AlignWidths.this 388 /** Instance returned will be shared with any other widgets of same widgetID.
389 *
390 * Also ensures each widget sharing an instance expects the same number of columns. */
391 static AlignColumns getInstance (widgetID id, myIt columns) {
392 AlignColumns* p = id in instances;
393 if (p) {
394 if (p.minWidth.length != columns)
395 throw new GuiException ("AlignColumns: no. of columns varies between sharing widgets (code error)");
396 return *p;
397 } else {
398 auto a = new AlignColumns (columns);
399 instances[id] = a;
400 return a;
401 }
402 }
403
404 /** Create an instance. After creation, the number of columns can only be changed by calling
405 * reset.
406 *
407 * After creation, minimal widths should be set for all columns (minWidth) and
408 * setWidths must be called before other functions are used. */
385 this (myIt columns) { 409 this (myIt columns) {
386 super (columns); 410 reset (columns);
411 }
412
413 /** Reset all column information (only keep set callbacks).
414 *
415 * Widths should be set after calling, as on creation. */
416 void reset (myIt columns) {
417 if (columns < 1)
418 throw new GuiException("AlignColumns: created with <1 column");
419 minWidth = new wdim[columns];
420 sizable = new bool[columns];
421 width = null; // enforce calling setWidths after this
422 firstSizable = -1;
423 lastSizable = -1;
424 spare = 0;
425 }
426
427 /** Initialize widths as minimal widths. */
428 void setWidths () {
429 if (!width) {
430 width = minWidth.dup;
431 initCalc;
432 }
433 }
434 /** Initialize widths from supplied list, checking validity. */
435 void setWidths (wdim[] data) {
436 if (!width) {
437 // Set to provided data:
438 width = data;
439 // And check sizes are valid:
440 foreach (i, m; minWidth) {
441 // if fixed width or width is less than minimum:
442 if (!sizable[i] || width[i] < m) {
443 width[i] = m;
444 }
445 }
446 initCalc;
447 }
387 } 448 }
388 449
389 /** Add a callback to be called to notify changes in a column's width. 450 /** Add a callback to be called to notify changes in a column's width.
390 * 451 *
391 * All callbacks added are called on a width change so that multiple objects may share a 452 * All callbacks added are called on a width change so that multiple objects may share a
394 assert (setCW, "CellAlign.this: setCW is null (code error)"); 455 assert (setCW, "CellAlign.this: setCW is null (code error)");
395 setWidthCb ~= setCW; 456 setWidthCb ~= setCW;
396 return this; 457 return this;
397 } 458 }
398 459
399 /** Generate position infomation and return total width of columns. */ 460 /** Get the row/column of relative position l.
400 wdim genPositions () { 461 *
401 pos.length = minWidth.length; 462 * returns:
402 463 * -i if in space to left of col i, or i if on col i, or -(num cols + 1) if in $(I spare)
403 wdim x = 0; 464 * space to right of last column. */
404 foreach (i, w; width) {
405 pos[i] = x;
406 x += w + spacing;
407 }
408 return x - spacing;
409 }
410
411 // Get the row/column a click occured in
412 // Returns -i if in space to left of col i, or i if on col i
413 myDiff getCell (wdim l) { 465 myDiff getCell (wdim l) {
414 myDiff i = minWidth.length - 1; // starting from right... 466 myDiff i = minWidth.length - 1; // starting from right...
415 while (l < pos[i]) { // decrement while left of this column 467 while (l < pos[i]) { // decrement while left of this column
416 debug assert (i > 0, "getCell: l < pos[0] (code error)"); 468 debug assert (i > 0, "getCell: l < pos[0] (code error)");
417 --i; 469 --i;
418 } // now (l >= pos[i]) 470 } // now (l >= pos[i])
419 if (l >= pos[i] + width[i]) { // between columns 471 if (l >= pos[i] + width[i]) { // between columns or in spare space after last column
420 debug assert (i+1 < minWidth.length, "getCell: l >= pos[$-1] + width[$-1] (code error)"); 472 debug assert (i+1 < minWidth.length ||
473 l < pos[i] + width[i] + spare,
474 "getCell: l >= total width (code error)");
421 return -i - 1; // note: i might be 0 so cannot just return -i 475 return -i - 1; // note: i might be 0 so cannot just return -i
422 } 476 }
423 return i; 477 return i;
424 } 478 }
425 479
426 // Calculate resizeU/resizeD, and return true if unable to resize. 480 /** Adjust total size with direction dir.
427 bool findResize (wdim l) { 481 *
482 * nw should be at least the minimal width. */
483 wdim resizeWidth (wdim nw, int dir) {
484 if (nw < mw) {
485 debug logger.warn ("Widget dimension set below minimal");
486 nw = mw;
487 }
488 if (nw == w) return w;
489
490 wdim diff = nw - w;
491 if (firstSizable == -1) {
492 spare += diff;
493 w = nw;
494 } else
495 adjustCellSizes (diff, (dir == -1 ? lastSizable : firstSizable), dir);
496
497 debug if (nw != w) {
498 logger.trace ("resizeWidth to {} failed, new width: {}",nw,w);
499 /+ Also print column widths & positions:
500 logger.trace ("resizeWidth to {} failed! Column dimensions and positions:",nw);
501 foreach (i,w; width)
502 logger.trace ("\t{}\t{}", w,pos[i]);+/
503 }
504 return w;
505 }
506
507 /** Calculate resizeU/resizeD, and return true if unable to resize.
508 *
509 * This and resizeCols are for moving dividers between cells. */
510 bool findResizeCols (wdim l) {
428 resizeU = -getCell (l); // potential start for upward-resizes 511 resizeU = -getCell (l); // potential start for upward-resizes
429 if (resizeU <= 0) return true; // not on a space between cells 512 if (resizeU <= 0 || resizeU > minWidth.length)
430 resizeD = resizeU - 1; // potential start for downward-resizes 513 return true; // not on a space between cells or in spare space after last cell
431 514 resizeD = resizeU - 1; // potential start for downward-resizes
515
432 while (!sizable[resizeU]) { // find first actually resizable column (upwards) 516 while (!sizable[resizeU]) { // find first actually resizable column (upwards)
433 ++resizeU; 517 ++resizeU;
434 if (resizeU >= minWidth.length) { // cannot resize 518 if (resizeU >= minWidth.length) { // cannot resize
435 resizeU = -1; 519 resizeU = -1;
436 return true; 520 return true;
444 return true; 528 return true;
445 } 529 }
446 } 530 }
447 531
448 return false; // can resize 532 return false; // can resize
533 }
534 /// Resize columns based on findResizeCols
535 void resizeCols (wdim diff)
536 {
537 if (resizeU <= 0) return;
538
539 // do shrinking first (in case we hit the minimum)
540 if (diff >= 0) {
541 diff = -adjustCellSizes (-diff, resizeU, 1);
542 adjustCellSizes (diff, resizeD, -1);
543 } else {
544 diff = -adjustCellSizes (diff, resizeD, -1);
545 adjustCellSizes (diff, resizeU, 1);
546 }
547 }
548
549 /** Intitialization triggered by setWidths.
550 *
551 * Calculates first/lastSizable from sizable, minimal width and positions. */
552 private void initCalc () {
553 /* Calculate the minimal width of all columns plus spacing. */
554 mw = spacing * cast(wdim)(minWidth.length - 1);
555 foreach (w; minWidth)
556 mw += w;
557
558 genPositions;
559 foreach (i,s; sizable) {
560 if (s) {
561 firstSizable = i;
562 goto gotFirst;
563 }
564 }
565 return; // none resizable - don't search for lastSizable
566 gotFirst:
567
568 foreach_reverse (i,s; sizable) {
569 if (s) {
570 lastSizable = i;
571 return; // done
572 }
573 }
574 }
575
576 /* Generate position infomation for each column and set w. */
577 private void genPositions () {
578 pos.length = minWidth.length;
579
580 w = 0;
581 foreach (i, cw; width) {
582 pos[i] = w;
583 w += cw + spacing;
584 }
585 w -= spacing;
449 } 586 }
450 587
451 /* Adjust the total size of rows/columns (including spacing) by diff. 588 /* Adjust the total size of rows/columns (including spacing) by diff.
452 * 589 *
453 * Params: 590 * Params:
459 * The amount adjusted. This may be larger than diff, since cellD is clamped by cellDMin. 596 * The amount adjusted. This may be larger than diff, since cellD is clamped by cellDMin.
460 * 597 *
461 * Note: Check variable used for start is valid before calling! If a non-sizable column's 598 * Note: Check variable used for start is valid before calling! If a non-sizable column's
462 * index is passed, this should get increased (if diff > 0) but not decreased. 599 * index is passed, this should get increased (if diff > 0) but not decreased.
463 */ 600 */
464 wdim adjustCellSizes (wdim diff, myDiff start, int incr) 601 private wdim adjustCellSizes (wdim diff, myDiff start, int incr)
465 in { 602 in {
466 assert (width, "CellAlign.adjustCellSizes: width is null (code error)"); 603 assert (width.length == minWidth.length, "CellAlign.adjustCellSizes: width is null (code error)");
467 // Most likely if passed negative when sizing is disabled: 604 // Most likely if passed negative when sizing is disabled:
468 assert (start >= 0 && start < minWidth.length, "adjustCellSizes: invalid start"); 605 assert (start >= 0 && start < minWidth.length, "adjustCellSizes: invalid start");
469 debug assert (incr == 1 || incr == -1, "adjustCellSizes: invalid incr"); 606 debug assert (incr == 1 || incr == -1, "adjustCellSizes: invalid incr");
470 } body { 607 } body {
471 debug scope(failure) 608 debug scope(failure) logger.trace ("adjustCellSizes: failure");
472 logger.trace ("adjustCellSizes: failure");
473 myDiff i = start; 609 myDiff i = start;
474 if (diff > 0) { // increase size of first resizable cell 610 if (diff > 0) { // increase size of first resizable cell
475 width[i] += diff; 611 width[i] += diff;
476 foreach (dg; setWidthCb) 612 foreach (dg; setWidthCb)
477 dg(i, width[i], incr); 613 dg(i, width[i], incr);
510 646
511 genPositions; 647 genPositions;
512 return diff; 648 return diff;
513 } 649 }
514 650
515 void resize (wdim diff)
516 {
517 if (resizeU <= 0) return;
518
519 // do shrinking first (in case we hit the minimum)
520 if (diff >= 0) {
521 diff = -adjustCellSizes (-diff, resizeU, 1);
522 adjustCellSizes (diff, resizeD, -1);
523 } else {
524 diff = -adjustCellSizes (diff, resizeD, -1);
525 adjustCellSizes (diff, resizeU, 1);
526 }
527 }
528
529 private:
530 wdim[] pos; // relative position (cumulative width[i-1] plus spacing)
531 wdim spacing; // used by genPositions (which cannot access the layout class's data)
532 // Callbacks used to actually adjust a column's width:
533 void delegate (myIt,wdim,int) setWidthCb[]; // set width of a column, with resize direction
534 }
535
536 /**************************************************************************************************
537 * Alignment device
538 *
539 * E.g. can control widths of columns within a grid, and provide sensible resizing, respecting the
540 * minimal width required by each cell in a column. Is not restricted to horizontal widths, but to
541 * ease descriptions, a horizontal context (column widths) is assumed.
542 *
543 * Cells should be of type IChildWidget.
544 *
545 * FIXME:
546 * Cells are not directly interacted with, but minimal widths for each column are passed, and
547 * callback functions are used to adjust the width of any column.
548 *************************************************************************************************/
549 //FIXME: how to set cell positions after resizes?
550 /+ FIXME - remove position information from here, removing need of horizontal alignment of cells
551 into columns. On resizing, don't permanently adjust sizes until callback ends (mouse button
552 released). Until then, always readjust from current "permanent" size. +/
553 class AlignWidths
554 {
555 /** Create an instance. After creation, the number of columns can only be changed by calling
556 * reset.
557 *
558 * After creation, minimal widths should be set for all columns (minWidth) and
559 * setWidths must be called before other functions are used. */
560 this (myIt columns) {
561 reset (columns);
562 }
563
564 /** Reset all column information (only keep set callbacks).
565 *
566 * Widths should be set after calling, as on creation. */
567 void reset (myIt columns) {
568 if (columns < 1)
569 throw new GuiException("AlignWidths: created with <1 column");
570 minWidth = new wdim[columns];
571 width = null; // enforce calling setWidths after this
572 }
573
574 /** Initialize widths as minimal widths. */
575 void setWidths () {
576 width = minWidth.dup;
577 }
578 /** Initialize widths from supplied list, checking validity. */
579 void setWidths (wdim[] data) {
580 // Set to provided data:
581 width = data;
582 // And check sizes are valid:
583 foreach (i, m; minWidth)
584 // if fixed width or width is less than minimum:
585 if (!sizable[i] || width[i] < m)
586 width[i] = m;
587 }
588
589 /** Minimal width for each column. 651 /** Minimal width for each column.
590 * 652 *
591 * Initialized to zero. Each class using this AlignWidths should, for each column, increase 653 * Initialized to zero. Each class using this AlignColumns should, for each column, increase
592 * this value to the maximum of the minimal widths (in other words, set 654 * this value to the maximum of the minimal widths (in other words, set
593 * minWidth[i] = max(minWidth[i], cell.minWidth) for each cell in column i). */ 655 * minWidth[i] = max(minWidth[i], cell.minWidth) for each cell in column i). */
594 wdim[] minWidth; // minimal widths (set by genCachedConstructionData) 656 wdim[] minWidth; // minimal widths (set by genCachedConstructionData)
595 657
596 /** For each column i, sizable[i] is true if that column is resizable. 658 /** For each column i, sizable[i] is true if that column is resizable.
597 * firstSizable and lastSizable are the indicies of the first/last resizable column (negative
598 * if none are resizable).
599 * 659 *
600 * Set along with minWidth before calling setWidths. */ 660 * Set along with minWidth before calling setWidths. */
601 bool[] sizable; // set by genCachedConstructionData 661 bool[] sizable; // set by genCachedConstructionData
602 /// ditto 662
603 myDiff firstSizable, lastSizable; // set by genCachedConstructionData 663 /** Current width, relative position (for each column)
604
605 /** Current width for each column.
606 * 664 *
607 * Treat as READ ONLY! */ 665 * Treat as READ ONLY! */
608 wdim[] width; // only adjusted within the class 666 wdim[] width; // only adjusted within the class
609 protected: 667 wdim[] pos; /// ditto
610 myDiff resizeD, // resize down from this index (<0 if not resizing) 668 protected:
669 myDiff resizeD, // resizeCols works down from this index (<0 if not resizing)
611 resizeU; // and up from this index 670 resizeU; // and up from this index
671 wdim spacing; // used by genPositions (which cannot access the layout class's data)
672 wdim spare; // fixed size only: extra blank space filler
673 wdim w,mw; // current & minimal widths
674 /* indicies of the first/last resizable column (negative if none are resizable). */
675 myDiff firstSizable = -1, lastSizable = -1; // set by calcFLSbl
676 // Callbacks used to actually adjust a column's width:
677 void delegate (myIt,wdim,int) setWidthCb[]; // set width of a column, with resize direction
678
679 static HashMap!(widgetID,AlignColumns) instances;
680 static this () {
681 instances = new HashMap!(widgetID,AlignColumns);
682 }
612 } 683 }
613 684
614 // Index types. Note that in some cases they need to hold negative values. 685 // Index types. Note that in some cases they need to hold negative values.
615 // int is used for resizing direction (although ptrdiff_t would be more appropriate), 686 // int is used for resizing direction (although ptrdiff_t would be more appropriate),
616 // since the value must always be -1 or +1. 687 // since the value must always be -1 or +1.