Changeset 40:b28d7adc786b

Show
Ignore:
Timestamp:
05/08/08 11:05:51 (8 months ago)
Author:
Diggory Hardy <diggory.hardy@gmail.com>
branch:
default
convert_revision:
ff63c69d22e0aaffd8838ad4b6616906d6960894
Message:

Made GUI more robust to mutable data changes and improved much of GridLayoutWidget?'s code.

committer: Diggory Hardy <diggory.hardy@gmail.com>

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • codeDoc/jobs.txt

    r37 r40  
    44 
    55In progress: 
     6Make a widget with multiple sizable rows/cols. 
     7Implement row/col sizing. 
    68 
    79 
  • data/conf/gui.mtt

    r38 r40  
    88<int|x=150> 
    99<int|y=200> 
    10 <int[][int]|widgetData=[0:[0xB004,1,3,3,1,5],3:[0x4010,160,160],5:[0xB004,3,1,6,1,6],6:[0x4010,60,60],1:[0x3001]]> 
     10<int[][int]|widgetData=[0:[0xB004,5,5,2,1,2,1,2,1,1,1,1,1,2,1,2,1,2,1,1,1,1,1,2,1,2,1,2],1:[0x3001],2:[0x4010,75,75]]> 
  • mde/gui/exception.d

    r39 r40  
    4848    } 
    4949} 
    50  
    51 /// Thrown when a Widget class's adjust() is called with invalid data. 
    52 class MutableDataException : WindowLoadException 
    53 { 
    54     this () {   // Default, by Widget class's this 
    55         super ("Bad widget mutable data"); 
    56     } 
    57     this (char[] msg) { // From createWidget 
    58         super (msg); 
    59     } 
    60 } 
  • mde/gui/widget/Ifaces.d

    r39 r40  
    7676    /** Called after creating widgets to adjust size & other mutable attributes from saved data. 
    7777     * 
     78     * As for setSize, setPosition should be called afterwards. 
     79     * 
    7880     * Each widget should call adjust on each of its sub-widgets in turn with data, each time 
    7981     * replacing data by the return value of the call. It should then take its own mutable data 
    8082     * from the beginning of the array and return the remainder of the array. 
    8183     * 
    82      * Throws: on error, throw a MutableDataException. */ 
     84     * Adjust should handle errors gracefully by reverting to default values and not throwing. 
     85     * This is because the creation data and the user's mutable data may be stored separately and 
     86     * become out-of-sync during an update. */ 
    8387    int[] adjust (int[] data); 
    8488     
    85     /** Output data suitible for recreating the widget (data to be passed to this()). */ 
     89    /** Output data suitible for recreating the widget (data to be passed to this()). 
     90     * 
     91     * Creation data is data only changed when the gui is edited. */ 
    8692    int[] getCreationData (); 
    8793     
    8894    /** Output data containing the widget's current adjustments (data to be passed to adjust()). 
     95     * 
     96     * Mutable data is data which can be changed during normal gui use, such as the size of 
     97     * resizible widgets or current tab of a tab widget. 
     98     * 
    8999     * Should be a concatenation of each sub-widget's mutable data and the widget's own. */ 
    90100    int[] getMutableData (); 
  • mde/gui/widget/Window.d

    r39 r40  
    5959    } body { 
    6060        // Check data was loaded: 
    61         if (widgetData is null || mutableData is null
    62             throw new WindowLoadException ("No widget/mutable data"); 
     61        if (widgetData is null
     62            throw new WindowLoadException ("No widget creation data"); 
    6363         
    6464        gui_ = gui; 
     
    7373        widgetData = null;  // data is no longer needed: allow GC to collect (cannot safely delete) 
    7474         
     75        // Note: this should return an empty array, but nothing much should happen if it's not empty: 
    7576        widget.adjust (mutableData);    // adjust/set size, etc. 
    7677        mutableData = null;             // no longer needed 
     
    112113        /+ NOTE: currently editing is impossible... 
    113114        if (edited) {   // only save the widget creation data if it's been adjusted: 
    114             addSaveData (widget);       // generate widget save data 
     115            addCreationData (widget);   // generate widget save data 
    115116            dlg (INTAA, WDGD, parseFrom!(int[][int]) (widgetData)); 
    116117        }+/ 
  • mde/gui/widget/layout.d

    r39 r40  
    2222/** Encapsulates a grid of Widgets. 
    2323* 
    24 * Since a grid with either dimension zero is not useful, there must be at least one sub-widget. */ 
     24* Currently there is no support for changing number of cells, sub-widgets or sub-widget properties 
     25* (namely isW/HSizable and minimal size) after this() has run. 
     26
     27* Since a grid with either dimension zero is not useful, there must be at least one sub-widget. 
     28
     29* The grid has no border but has spacing between widgets. */ 
    2530class GridLayoutWidget : Widget 
    2631{ 
     
    4449            subWidget = window.makeWidget (data[i+3]); 
    4550        } 
     51         
     52        // Calculate cached construction data 
     53        genCachedConstructionData; 
    4654    } 
    4755     
     
    5058        foreach (widget; subWidgets) 
    5159            data = widget.adjust (data); 
    52         if (data.length < rows + cols) throw new MutableDataException; 
    5360         
    5461        /* We basically short-cut setSize by loading previous col/row sizes and doing the final 
    55          * calculations. There isn't checks that the data is valid/up-to-date... worst case is too 
    56          * small overlapping widgets or huge ones? 
     62         * calculations. 
    5763         * Note: if setSize gets called afterwards, it should have same dimensions and so not do 
    5864         * anything. */ 
    59         colW = data[0..cols]; 
    60         rowH = data[cols..rows+cols]; 
    61         setColRowSizes; 
     65         
     66        int lenUsed = 0; 
     67        if (data.length < rows + cols) {    // data error; use defaults 
     68            colW = colWMin.dup; 
     69            rowH = rowHMin.dup; 
     70        } else {                            // sufficient data 
     71            lenUsed = rows+cols; 
     72            colW = data[0..cols]; 
     73            rowH = data[cols..lenUsed]; 
     74             
     75            // Check row sizes are valid: 
     76            //NOTE: this could be made optional 
     77            foreach (i, inout w; colW) 
     78                if (w < colWMin[i]) w = colWMin[i]; 
     79            foreach (i, inout h; rowH) 
     80                if (h < rowHMin[i]) h = rowHMin[i]; 
     81        } 
     82         
     83        genCachedMutableData; 
    6284        w = colW[$-1] + colX[$-1]; 
    6385        h = rowY[$-1] + rowH[$-1]; 
    6486         
    65         return data[rows+cols..$]; 
     87        return data[lenUsed..$]; 
    6688    } 
    6789     
     
    87109     
    88110    bool isWSizable () { 
    89         if (colSizable == -2) {     // check whether any columns are resizable 
    90             for1: 
    91             for (uint i = 0; i < cols; ++i) {                       // for each column 
    92                 for (uint j = 0; j < subWidgets.length; j += cols)  // for each row 
    93                     if (!subWidgets[i+j].isWSizable)        // column not resizable 
    94                         continue for1;                      // continue the outer for loop 
    95                  
    96                 // column is resizable if we get to here 
    97                 colSizable = i; 
    98                 goto break1;        // use goto in lieu of for...else 
    99             } 
    100              
    101             // if we get here, no resizable column was found 
    102             colSizable = -1; 
    103              
    104             break1:; 
    105         } 
    106          
    107         if (colSizable >= 0) return true; 
    108         else return false; 
     111        return (sizableCols.length != 0); 
    109112    } 
    110113     
    111114    bool isHSizable () { 
    112         if (rowSizable == -2) {     // check whether any columns are resizable 
    113             for2: 
    114             for (uint i = 0; i < subWidgets.length; i += cols) {    // for each row 
    115                 for (uint j = 0; j < cols; ++j)                     // for each column 
    116                     if (!subWidgets[i+j].isHSizable) 
    117                         continue for2; 
    118                  
    119                 rowSizable = i / cols;  // the current row 
    120                 goto break2; 
    121             } 
    122              
    123             rowSizable = -1; 
    124              
    125             break2:; 
    126         } 
    127          
    128         if (rowSizable >= 0) return true; 
    129         else return false; 
     115        return (sizableRows.length != 0); 
    130116    } 
    131117     
    132118    /* Calculates the minimal size from all rows and columns of widgets. */ 
    133119    void getMinimalSize (out int mw, out int mh) { 
    134         // If rowHMin & colWMin are null, calculate them. They are set null whenever the contents 
    135         // or the contents' minimal size change, as well as when this widget is created. 
    136         if (rowHMin is null) 
    137             genMinRowColSizes; 
    138          
    139         // Calculate the size, starting with the spacing: 
    140         mh = window.renderer.layoutSpacing;     // use temporarily 
    141         mw = mh * (cols - 1); 
    142         mh *= (rows - 1); 
    143          
    144         foreach (x; colWMin)            // add the column/row's dimensions 
    145             mw += x; 
    146         foreach (x; rowHMin) 
    147             mh += x; 
     120        mw = this.mw; 
     121        mh = this.mh; 
    148122    } 
    149123     
    150124    void setSize (int nw, int nh) { 
    151         /* For each of width and height, there are several cases: 
    152          *  [new value is] more than old value 
    153          *  ->  enlarge any row/column 
    154          *  same as old value 
    155          *  ->  do nothing 
    156          *  more than min but less than current value 
    157          *  ->  find an enlarged row/col and reduce size 
    158          *  ->  repeat if necessary 
    159          *  minimal value or less 
    160          *  -> clamp to min and use min col/row sizes 
    161          */ 
    162         // FIXME: implement! 
    163          
    164         // Step 1: calculate the minimal row/column sizes. 
    165         alias w mw;             // no need for extra vars, just use these 
    166         alias h mh; 
    167         getMinimalSize (mw, mh); 
    168         colW = colWMin.dup;     // start with these dimensions, and increase if necessary 
    169         rowH = rowHMin.dup;     // duplicate, because we may want to resize without recalculating *Min 
    170          
    171         // Step 2: clamp nw/nh or expand a column/row to achieve the required size 
    172         if (nw <= mw) nw = mw;  // clamp to minimal size 
    173         else { 
    174             if (isWSizable)     // calculates colSizable; true if any is resizable 
    175                 colW[colSizable] += nw - mw; // new width 
    176             else                // no resizable column; so force the last one 
    177                 colW[$-1] += nw - mw; 
    178         } 
    179          
    180         if (nh <= mh) nh = mh; 
    181         else { 
    182             if (isHSizable) 
    183                 rowH[rowSizable] += nh - mh; 
    184             else 
    185                 rowH[$-1] += nh - mh; 
    186         } 
    187          
    188         w = nw; 
    189         h = nh; 
    190          
    191         // Step 3: set each sub-widget's size. 
    192         // Step 4: calculate the column and row positions 
    193         setColRowSizes; 
    194          
    195         // Step 5: position needs resetting 
     125        // Step 1: calculate the row/column sizes. 
     126        setSizeImpl!(true)  (nw); 
     127        setSizeImpl!(false) (nh); 
     128         
     129        // Step 2: calculate the row/column offsets (positions) and set the sub-widgets sizes. 
     130        genCachedMutableData; 
     131         
     132        // Step 3: position needs to be set 
    196133        // Currently this happens by specifying that setPosition should be run after setSize. 
    197134    } 
     
    244181     
    245182private: 
    246     void genMinRowColSizes () { 
    247         // Find the sizes of all subWidgets 
    248         int[] widgetW = new int[subWidgets.length]; // dimensions 
    249         int[] widgetH = new int[subWidgets.length]; 
    250         foreach (i,widget; subWidgets) 
    251             widget.getMinimalSize (widgetW[i],widgetH[i]); 
     183    /* Calculations which need to be run whenever a new sub-widget structure is set 
     184     * (i.e. to produce cached data calculated from construction data). */ 
     185    void genCachedConstructionData () { 
     186        // Calculate the minimal column and row sizes: 
     187        colWMin = new int[cols];    // set length, making sure the arrays are initialised to zero 
     188        rowHMin = new int[rows]; 
     189        int ww, wh;     // sub-widget minimal sizes 
     190        foreach (i,widget; subWidgets) { 
     191            widget.getMinimalSize (ww, wh); 
    252192             
    253         // Find the minimal row heights and column widths (non cumulative) 
    254         colWMin = new int[cols];    // set length 
    255         rowHMin = new int[rows]; 
    256         for (uint i = 0; i < subWidgets.length; ++i) { 
    257             uint n; 
    258             n = i % cols;           // column 
    259             if (colWMin[n] < widgetW[i]) colWMin[n] = widgetW[i]; 
     193            // Increase dimensions if current minimal size is larger: 
     194            uint n = i % cols;      // column 
     195            if (colWMin[n] < ww) colWMin[n] = ww; 
    260196            n = i / cols;           // row 
    261             if (rowHMin[n] < widgetH[i]) rowHMin[n] = widgetH[i]; 
    262         } 
    263     } 
    264      
    265     void setColRowSizes () { 
     197            if (rowHMin[n] < wh) rowHMin[n] = wh; 
     198        } 
     199         
     200         
     201        // Calculate the overall minimal size, starting with the spacing: 
     202        mh = window.renderer.layoutSpacing;     // use temporarily 
     203        mw = mh * (cols - 1); 
     204        mh *= (rows - 1); 
     205         
     206        foreach (x; colWMin)            // add the column/row's dimensions 
     207            mw += x; 
     208        foreach (x; rowHMin) 
     209            mh += x; 
     210         
     211         
     212        // Find which cols/rows are resizable: 
     213        sizableCols = null;     // clear these because we append to them 
     214        sizableRows = null; 
     215         
     216        forCols: 
     217        for (uint i = 0; i < cols; ++i) {                       // for each column 
     218            for (uint j = 0; j < subWidgets.length; j += cols)  // for each row 
     219                if (!subWidgets[i+j].isWSizable)        // column not resizable 
     220                    continue forCols;                   // continue the outer for loop 
     221                 
     222            // column is resizable if we get to here 
     223            sizableCols ~= i; 
     224        } 
     225         
     226        forRows: 
     227        for (uint i = 0; i < subWidgets.length; i += cols) {    // for each row 
     228            for (uint j = 0; j < cols; ++j)                     // for each column 
     229                if (!subWidgets[i+j].isHSizable) 
     230                    continue forRows; 
     231                 
     232            sizableRows ~= i / cols;    // the current row 
     233        } 
     234    } 
     235     
     236    /* Calculations which need to be run whenever resizing occurs (or deeper alterations) 
     237     * (i.e. to produce cached data calculated from construction and mutable data). */ 
     238    void genCachedMutableData () { 
    266239        // Calculate column and row locations: 
    267240        colX.length = cols; 
     
    286259    } 
    287260     
     261    /* setSize generalised for either dimension; w/h is renamed d, col/row renamed cell. */ 
     262    void setSizeImpl(bool W) (int nd) { 
     263        static if (W) { 
     264            alias w         d; 
     265            alias mw        md; 
     266            alias colW      cellD; 
     267            alias colWMin   cellDMin; 
     268            alias sizableCols sizableCells; 
     269        } else { 
     270            alias h         d; 
     271            alias mh        md; 
     272            alias rowH      cellD; 
     273            alias rowHMin   cellDMin; 
     274            alias sizableRows sizableCells; 
     275        } 
     276        // Could occur if adjust isn't called first, but this would be a code error: 
     277        assert (cellD !is null, "setSizeImpl: cellD is null"); 
     278         
     279        /* For each of width and height, there are several cases: 
     280        *  [new value is] more than old value 
     281        *  ->  enlarge any row/column 
     282        *  same as old value 
     283        *  ->  do nothing 
     284        *  more than min but less than current value 
     285        *  ->  find an enlarged row/col and reduce size 
     286        *  ->  repeat if necessary 
     287        *  minimal value or less 
     288        *  -> clamp to min and use min col/row sizes 
     289        */ 
     290        if (nd > d) {       // expand (d < nd) 
     291            if (sizableCells.length) {               // check there is a resizable col/row 
     292                cellD[sizableCells[$-1]] += nd - d;  // new size 
     293                d = nd; 
     294            } 
     295        } else if (nd < d) { 
     296            if (nd > md) {  // shrink (md < nd < d) 
     297                int toReduce = nd - d;  // negative 
     298                foreach_reverse (cell; sizableCells) { 
     299                    cellD[cell] += toReduce; 
     300                    toReduce = cellD[cell] - cellDMin[cell]; 
     301                    if (toReduce < 0)   // reduced too far 
     302                        cellD[cell] = cellDMin[cell]; 
     303                    else                // ok; cellD >= cellDMin 
     304                        break; 
     305                } 
     306                d = nd; 
     307            } else {        // clamp  (nd <= md) 
     308                cellD = cellDMin.dup;   // duplicate so future edits don't affect cellDMin 
     309                d = md; 
     310            } 
     311        }                   // third possibility: nd = d 
     312    } 
     313     
    288314protected: 
     315    // Construction data (saved): 
    289316    int cols, rows;     // number of cells in grid 
    290      
    291     int colSizable = -2;// 0..cols-1 means this column is resizable 
    292     int rowSizable = -2;// -2 means not calculated yet, -1 means not resizable 
    293      
    294     int[] colWMin;      // minimal column width 
    295     int[] rowHMin;      // minimal row height 
    296     int[] colW;         // column width (widest widget) 
    297     int[] rowH;         // row height (highest widget in the row) 
    298      
    299     int[] colX;         // cumulative colW[i-1] + padding (add x to get column's left x-coord) 
    300     int[] rowY;         // cumulative rowH[i-1] + padding 
    301      
    302317    IWidget[] subWidgets;   // all widgets in the grid (by row): 
    303318    /* SubWidget order:    [ 0 1 ] 
    304319    *                      [ 2 3 ] */ 
     320     
     321    // Cached data calculated from construction data: 
     322    int[] colWMin;      // minimal column width 
     323    int[] rowHMin;      // minimal row height 
     324    int mw, mh;         // minimal dimensions 
     325     
     326    int[] sizableCols;   // all resizable columns / rows, empty if not resizable 
     327    int[] sizableRows;   // resizing is done from last entry 
     328     
     329    // Mutable data (saved): 
     330    int[] colW;         // column width (widest widget) 
     331    int[] rowH;         // row height (highest widget in the row) 
     332     
     333    // Cached data calculated from construction and mutable data: 
     334    int[] colX;         // cumulative colW[i-1] + padding (add x to get column's left x-coord) 
     335    int[] rowY;         // cumulative rowH[i-1] + padding 
    305336}