Mercurial > projects > mde
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. |