Changeset 45:0fd51d2c6c8a

Show
Ignore:
Timestamp:
05/22/08 06:34:09 (8 months ago)
Author:
Diggory Hardy <diggory.hardy@gmail.com>
branch:
default
convert_revision:
21abf12c3950bf3ec676e9aea0d1c9bd80087231
Message:

Several changes to resising windows and layout widgets. This commit still has some bugs.

Moved the implementable widgets from mde.gui.widget.Widget to miscWidgets, leaving base widgets in Widget.
Rewrote some of GridLayoutWidget?'s implementation. Made many operations general to work for either columns or rows. Some optimisations were intended but ended up being removed due to problems.
Allowed layout's to resize from either direction (only with window resizes).

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

Files:

Legend:

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

    r44 r45  
    55In progress: 
    66Implementing font rendering 
     7Reading ft tutorial 
     8Use cartesian coordinates? Or not? 
     9Make layout's adjustCellSizes method call setSize? 
     10More resizing stuff... 
    711 
    812 
     
    1014To do (importance 0-5: 0 pointless, 1 no obvious impact now, 2 todo sometime, 3 useful, 4 important, 5 urgent): 
    1115Also see todo.txt and FIXME/NOTE comment marks. 
     165   mergetag crashes with no ending > on a tag and doesn't add header data when comments are included! 
    12174   Not guaranteed to catch up-click ending callback! Appears not to be a problem... 
    13184   OutOfMemoryException is not currently checked for − it should be at least in critical places (use high-level catching of all errors?). 
     
    5257 
    5358Done (for git log message): 
     59Moved the implementable widgets from mde.gui.widget.Widget to miscWidgets, leaving base widgets in Widget. 
     60Rewrote some of GridLayoutWidget's implementation. Made many operations general to work for either columns or rows. Some optimisations were intended but ended up being removed due to problems. 
     61Allowed layout's to resize from either direction (only with window resizes). 
  • data/conf/gui.mtt

    r44 r45  
    99<int|y=200> 
    1010<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:[0x2]]> 
     11{WEmbedded} 
     12<int|x=20> 
     13<int|y=100> 
     14<int[][int]|widgetData=[1:[0x4010,50,50],2:[0xB004,3,1,3,1,3],3:[0x3001],0:[0xB004,3,1,3,1,3]]> 
  • mde/gl/basic.d

    r44 r45  
    2525//BEGIN GL & window setup 
    2626void glSetup () { 
     27    glDisable(GL_DEPTH_TEST); 
     28    glEnable (GL_TEXTURE_2D); 
     29     
    2730    glClearColor (0.0f, 0.0f, 0.0f, 0.0f); 
    28     glDisable(GL_DEPTH_TEST); 
    2931     
    3032    glMatrixMode(GL_MODELVIEW); 
     
    3941     
    4042    // Make the top-left the origin (see gui/GUI notes.txt): 
     43    // Note that this only affects vertex operations − direct rasterisation operations are 
     44    // unaffected! 
    4145    glOrtho (0.0,w, h,0.0, -1.0, 1.0); 
    42     //glOrtho (0.0,1.0,0.0,1.0,-1.0,1.0); 
    4346     
    4447    glMatrixMode(GL_MODELVIEW); 
  • mde/gl/draw.d

    r32 r45  
    2525 
    2626import tango.time.Time;     // TimeSpan (type only; unused) 
     27import tango.util.log.Log : Log, Logger; 
     28 
     29private Logger logger; 
     30static this () { 
     31    logger = Log.getLogger ("mde.gl.draw"); 
     32} 
    2733 
    2834//BEGIN Drawing loop 
     
    3339    gui.draw (); 
    3440     
    35     glFlush(); 
     41    GLenum err = glGetError(); 
     42    if (err != GL_NO_ERROR) { 
     43        char[128] tmp; 
     44        logger.error (logger.format (tmp, "GL error: {}", err)); 
     45    } 
     46     
     47    glFinish();     // Use Finish rather than Flush to make sure gl is ready to swap buffers 
    3648    SDL_GL_SwapBuffers(); 
    3749} 
  • mde/gui/Gui.d

    r43 r45  
    6464         
    6565        IReader reader; 
    66         try { 
    67             reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_LOW, null, true); 
    68             reader.dataSecCreator = delegate mt.IDataSection(mt.ID id) { 
    69                 return new Window (id); 
    70             }; 
    71             reader.read; 
    72         } catch (mt.MTException e) { 
    73             logger.error ("Loading GUI aborted:"); 
    74             logger.error (e.msg); 
    75              
    76             return; 
    77         } 
     66        reader = confDir.makeMTReader (fileName, PRIORITY.HIGH_LOW, null, true); 
     67        reader.dataSecCreator = delegate mt.IDataSection(mt.ID id) { 
     68            return new Window (id); 
     69        }; 
     70        reader.read; 
    7871         
    7972        // Get the renderer 
    8073        char[]* p = RENDERER in reader.dataset.header.Arg!(char[]).Arg; 
    8174        if (p is null || *p is null) { 
    82             logger.error ("Loading GUI aborted: no renderer specified"); 
    83             return; 
    84         } 
    85         rendName = *p; 
     75            logger.warn ("no renderer specified: defaulting to Simple"); 
     76            rendName = "Simple"; 
     77        } 
     78        else 
     79            rendName = *p; 
    8680         
    8781        // get list 
     
    9589            } 
    9690            try { 
    97                 w.finalise (this); 
    98                 windows ~= w;       // only add if load successful 
     91                w.finalise (this); // if this fails, the window (but nothing else) won't work 
     92                windows ~= w;      // only add if load successful 
    9993            } catch (Exception e) { 
    10094                logger.error ("Window failed to load: " ~ e.msg); 
  • mde/gui/widget/Ifaces.d

    r41 r45  
    111111    bool isHSizable (); /// ditto 
    112112     
    113     /** Calculate the minimal size the widget could be shrunk to, taking into account 
    114      * child-widgets. */ 
     113    /** Calculate the minimal size the widget could be shrunk to (or its fixed size), taking into 
     114     * account child-widgets or other contents. */ 
    115115    void getMinimalSize (out int w, out int h); 
    116116     
     
    120120    /** Used to adjust the size. 
    121121     * 
    122      * setPosition should always be called after setSize (for layout widgets). Adding this 
    123      * restriction appears to be the most efficient approach without a lot more tests. 
     122     * w,h is the new size. The boolean parameters describe which direction to resize from and is 
     123     * only really relevent to layout widgets (see GridLayoutWidget's implementation). When 
     124     * calling, just past true,true if it doesn't matter. 
    124125     * 
    125126     * Implementation: 
     
    127128     * than that given by the parameters. Conversely, the size should not be reduced to the 
    128129     * widget's maximal size (if any) but expanded as necessary (alignment to be implemented). 
     130     * This should be true for both resizable and fixed widgets; fixed widgets may still be scaled 
     131     * to fill a whole row/column in a layout widget. 
    129132     * 
    130133     * If the actual size is needed, call getCurrentSize afterwards. */ 
    131     void setSize (int w, int h); 
     134    void setSize (int w, int h, bool, bool); 
    132135     
    133136    /** Set the current position (i.e. called on init and move). */ 
  • mde/gui/widget/Widget.d

    r44 r45  
    1414along with this program.  If not, see <http://www.gnu.org/licenses/>. */ 
    1515 
    16 /// GUI Widget module. 
     16/** GUI Widget module. 
     17 * 
     18 * This module contains some base widget classes suitable for widget classes to inherit. However, 
     19 * inheriting one of them is by no means necessary for a widget so long as the IWidget interface is 
     20 * implemented. */ 
    1721module mde.gui.widget.Widget; 
    1822 
    1923public import mde.gui.widget.Ifaces; 
    20 import mde.gui.exception; 
    2124import mde.gui.renderer.IRenderer; 
    22  
    23 import mde.resource.font; 
    24  
    25 import tango.io.Stdout; 
    2625 
    2726/** An abstract base widget class. 
     
    3231abstract class Widget : IWidget 
    3332{ 
    34     // Base this(); all widgets must at least check data.length is correct. 
     33//BEGIN Load and save 
     34    // Base this(). All widgets must check data.length is correct before calling this method. 
    3535    this (IWindow wind, int[] data) { 
    3636        window = wind; 
     
    4141    // still need to set their size. 
    4242    int[] adjust (int[] data) { 
    43         setSize (0,0); 
     43        setSize (0,0,true,true); 
    4444        return data; 
    4545    } 
    4646     
    47     bool isWSizable () {    return false;   } 
    48     bool isHSizable () {    return false;   } 
    49      
    50     // Widget type should always be the first value. 
     47    // Widget type should always be the first value. Any widget using extra creation data will need 
     48    // to reimplemnt this method. 
    5149    int[] getCreationData () { 
    5250        return [widgetType]; 
     
    5654        return []; 
    5755    } 
     56//END Load and save 
     57     
     58//BEGIN Size and position 
     59    bool isWSizable () {    return false;   } 
     60    bool isHSizable () {    return false;   } 
     61     
     62    /* Return minimal/fixed size. */ 
     63    void getMinimalSize (out int a, out int b) { 
     64        a = mw; 
     65        b = mh; 
     66    } 
    5867     
    5968    void getCurrentSize (out int cw, out int ch) { 
     
    6271    } 
    6372     
     73    /* Set size: minimal size is (mw,mh). Note that both resizable and fixed widgets should allow 
     74     * enlarging, so in both cases this is a correct implementation. */ 
     75    void setSize (int nw, int nh, bool, bool) { 
     76        w = (nw >= mw ? nw : mw); 
     77        h = (nh >= mh ? nh : mh); 
     78    } 
     79     
    6480    void setPosition (int nx, int ny) { 
    6581        x = nx; 
    6682        y = ny; 
    6783    } 
     84//END Size and position 
    6885     
    69     /* Return self, since we don't have child widgets and the method wouldn't have been called 
    70     * unless the location was over us. Valid for all widgets without children. */ 
     86//BEGIN Events 
     87    /* This method is only called when the location is over this widget; hence for all widgets 
     88     * without children this method is valid. */ 
    7189    IWidget getWidget (int,int) { 
    7290        return this; 
    7391    } 
    7492     
    75     /* Dummy event method (widget doesn't respond to events) */ 
     93    /* Dummy event method (suitable for all widgets which don't respond to events). */ 
    7694    void clickEvent (ushort cx, ushort cy, ubyte b, bool state) {} 
     95//END Events 
    7796     
    78     /* Basic draw method: draw the background (all widgets should do this) */ 
     97    /* Basic draw method: draw the background (all widgets should do this). */ 
    7998    void draw () { 
    8099        window.renderer.drawWidgetBack (x,y, w,h); 
     
    82101     
    83102protected: 
    84     final int widgetType;   // the type (stored for saving) 
    85     IWindow window;         // the enclosing window 
    86     int x, y;               // position 
    87     int w, h;               // size 
     103    final int widgetType;   // the type (stored for saving) 
     104    IWindow window;     // the enclosing window 
     105    int x, y;           // position 
     106    int w, h;           // size 
     107    int mw = 0, mh = 0;     // minimal or fixed size, depending on whether the widget is 
     108                    // resizible; both types of widgets should actually be expandable. 
    88109} 
    89 /** A base for fixed-size widgets. */ 
     110 
     111/** A base for fixed-size widgets taking their size from the creation data. */ 
    90112class FixedWidget : Widget { 
     113    // Check data.length is at least 3 before calling! 
    91114    this (IWindow wind, int[] data) { 
    92         w = wF
    93         h = hF
     115        mw = data[1]
     116        mh = data[2]
    94117        super (wind, data); 
     118        w = mw; 
     119        h = mh; 
    95120    } 
    96121     
    97122    int[] getCreationData () { 
    98         return [widgetType, wF, hF]; 
     123        return [widgetType, mw, mh]; 
    99124    } 
    100      
    101     /* Not resizable, so return current size. */ 
    102     void getMinimalSize (out int mw, out int mh) { 
    103         mw = wF; 
    104         mh = hF; 
    105     } 
    106      
    107     /* Ignore: a fixed size widget. */ 
    108     void setSize (int nw, int nh) { 
    109         w = (nw >= wF ? nw : wF); 
    110         h = (nh >= hF ? nh : hF); 
    111     } 
    112      
    113 protected: 
    114     int wF, hF;             // The "fixed" size, i.e. the preferred & minimal size 
    115125} 
    116126/** A base for resizable widgets. */ 
    117127class SizableWidget : Widget { 
     128    // Check data.length is at least 1 before calling! 
    118129    this (IWindow wind, int[] data) { 
    119130        super (wind, data); 
     
    122133    bool isWSizable () {    return true;    } 
    123134    bool isHSizable () {    return true;    } 
    124      
    125     /* Return zero. */ 
    126     void getMinimalSize (out int mw, out int mh) {} 
    127      
    128     /* Set size: a fully resizable widget. */ 
    129     void setSize (int nw, int nh) { 
    130         w = (nw >= 0 ? nw : 0); 
    131         h = (nh >= 0 ? nh : 0); 
    132     } 
    133      
    134     void draw () { 
    135         super.draw; 
    136          
    137         window.renderer.drawBlank (x,y, w,h); 
    138     } 
    139135} 
    140  
    141 //BEGIN Widgets 
    142 /// A fixed-size blank widget. 
    143 class FixedBlankWidget : FixedWidget 
    144 { 
    145     this (IWindow wind, int[] data) { 
    146         if (data.length != 3) throw new WidgetDataException; 
    147         wF = data[1]; 
    148         hF = data[2]; 
    149         super (wind, data); 
    150     } 
    151     void draw () { 
    152         super.draw; 
    153          
    154         window.renderer.drawBlank (x,y, w,h); 
    155     } 
    156 } 
    157  
    158 /// A completely resizable blank widget (initial size zero). 
    159 class SizableBlankWidget : SizableWidget 
    160 { 
    161     this (IWindow wind, int[] data) { 
    162         if (data.length != 1) throw new WidgetDataException; 
    163         super (wind, data); 
    164     } 
    165 } 
    166  
    167 /// First interactible widget 
    168 class ButtonWidget : FixedWidget 
    169 { 
    170     bool pushed = false;    // true if button is pushed in (visually) 
    171     // pushed is not the same as the button being clicked but not yet released. 
    172     // it is whether the mouse is over the button after being clicked. 
    173      
    174     this (IWindow wind, int[] data) { 
    175         if (data.length != 3) throw new WidgetDataException; 
    176         wF = data[1]; 
    177         hF = data[2]; 
    178         super (wind, data); 
    179     } 
    180      
    181     void draw () { 
    182         window.renderer.drawButton (x,y, w,h, pushed); 
    183     } 
    184      
    185     void clickEvent (ushort, ushort, ubyte b, bool state) { 
    186         if (b == 1 && state == true) { 
    187             pushed = true; 
    188             window.requestRedraw; 
    189             window.gui.addClickCallback (&clickWhileHeld); 
    190             window.gui.addMotionCallback (&motionWhileHeld); 
    191         } 
    192     } 
    193     // Called when a mouse motion/click event occurs while (held == true) 
    194     bool clickWhileHeld (ushort cx, ushort cy, ubyte b, bool state) { 
    195         if (b == 1 && state == false) { 
    196             if (cx >= x && cx < x+w && cy >= y && cy < y+h) // button event 
    197                 Stdout ("Button clicked!").newline; 
    198              
    199             pushed = false; 
    200             window.requestRedraw; 
    201             window.gui.removeCallbacks (cast(void*) this); 
    202              
    203             return true; 
    204         } 
    205         return false; 
    206     } 
    207     void motionWhileHeld (ushort cx, ushort cy) { 
    208         bool oldPushed = pushed; 
    209         if (cx >= x && cx < x+w && cy >= y && cy < y+h) pushed = true; 
    210         else pushed = false; 
    211         if (oldPushed != pushed) 
    212             window.requestRedraw; 
    213     } 
    214 } 
    215  
    216 /// Basic text widget 
    217 class TextWidget : FixedWidget 
    218 { 
    219     this (IWindow wind, int[] data) { 
    220         if (data.length != 1) throw new WidgetDataException; 
    221         wF = 100;   //FIXME: set properly 
    222         hF = 25; 
    223         super (wind,data); 
    224     } 
    225      
    226     void draw () { 
    227         super.draw(); 
    228         if (font is null) font = Font.get("/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf"); 
    229         font.drawStr (x,y, "Text Widget"); 
    230     } 
    231      
    232 protected: 
    233     static Font font; 
    234 } 
    235 //END Widgets 
  • mde/gui/widget/Window.d

    r41 r45  
    220220    } 
    221221     
    222     void setSize (int nw, int nh) { 
     222    void setSize (int nw, int nh, bool wB, bool hB) { 
    223223        getMinimalSize (w,h); 
    224224        if (nw > w) w = nw;     // expand if new size is larger, but don't go smaller 
     
    228228        yh = y+h; 
    229229         
    230         widget.setSize (w - border.l - border.r, h - border.t - border.b); 
     230        widget.setSize (w - border.l - border.r, h - border.t - border.b, wB, hB); 
    231231         
    232232        gui_.requestRedraw ();  // obviously necessary whenever the window's size is changed 
     
    304304    } 
    305305    void resizeCallback (ushort cx, ushort cy) { 
     306        debug scope(failure) 
     307                logger.trace ("resizeCallback: failure"); 
     308         
     309        // This function is only called if some resize is going to happen. 
     310        // To improve efficiency, store parameters to resize to. 
     311        int xSize = w, ySize = h;   // new size 
     312        int xDiff, yDiff;       // difference to new position 
     313        bool xHigh, yHigh;      // resize from positive side 
     314         
    306315        if (resizeType & RESIZE_TYPE.L) { 
    307             int mw, nw; 
    308             getMinimalSize (mw, nw);    // (only want mw) 
    309             nw = xDrag - cx; 
    310             if (nw < mw) nw = mw;       // clamp 
    311             mw = x + w - nw;            // reuse 
    312             setSize (nw, h); 
    313             setPosition (mw, y); 
     316            getMinimalSize (xDiff, xSize);  // (only want xDiff, temporarily used as mw) 
     317            xSize = xDrag - cx; 
     318            if (xSize < xDiff) xSize = xDiff;   // clamp 
     319            xDiff = w - xSize;          // now used as amount to move 
    314320        } 
    315321        else if (resizeType & RESIZE_TYPE.R) { 
    316             setSize (xDrag + cx, h)
    317             setPosition (x, y);         // required to call after setSize. 
     322            xSize = xDrag + cx
     323            xHigh = true; 
    318324        } 
    319325        if (resizeType & RESIZE_TYPE.T) { 
    320             int mh, nh; 
    321             getMinimalSize (nh, mh); 
    322             nh = yDrag - cy; 
    323             if (nh < mh) nh = mh; 
    324             mh = y + h - nh; 
    325             setSize (w, nh); 
    326             setPosition (x, mh); 
     326            getMinimalSize (ySize, yDiff); 
     327            ySize = yDrag - cy; 
     328            if (ySize < yDiff) ySize = yDiff; 
     329            yDiff = h - ySize; 
    327330        } 
    328331        else if (resizeType & RESIZE_TYPE.B) { 
    329             setSize (w, yDrag + cy); 
    330             setPosition (x, y); 
    331         } 
     332            ySize = yDrag + cy; 
     333            yHigh = true; 
     334        } 
     335         
     336        setSize (xSize, ySize, xHigh, yHigh); 
     337        if (xDiff != 0 || yDiff != 0) 
     338            setPosition (x + xDiff, y + yDiff); 
    332339    } 
    333340    bool endCallback (ushort cx, ushort cy, ubyte b, bool state) { 
  • mde/gui/widget/createWidget.d

    r44 r45  
    1717module mde.gui.widget.createWidget; 
    1818 
     19import mde.gui.widget.Ifaces; 
     20import mde.gui.exception : WidgetDataException; 
     21 
    1922// Widgets to create: 
    2023import mde.gui.widget.layout; 
    21 import mde.gui.widget.Widget; 
    22  
    23 import mde.gui.exception : WidgetDataException; 
     24import mde.gui.widget.miscWidgets; 
    2425 
    2526/** Create a widget of type data[0] (see enum WIDGET_TYPES) for _window window, with initialisation 
  • mde/gui/widget/layout.d

    r43 r45  
    2020import mde.gui.exception; 
    2121 
    22 import tango.io.Stdout; 
    2322debug { 
    2423    import tango.util.log.Log : Log, Logger; 
     
    4847        cols = data[2]; 
    4948        if (data.length != 3 + rows * cols) throw new WidgetDataException; 
    50         /* data.length >= 3 so besides checking the length is correct, this tells us: 
     49        /* data.length >= 4 so besides checking the length is correct, this tells us: 
    5150         *      rows * cols >= 4 - 3 = 1            a free check! 
    5251         * The only thing not checked is whether both rows and cols are negative, which would 
    53          * cause an exception when dynamic arrays are allocated later (and is unlikely). */ 
     52         * cause an exception when dynamic arrays are allocated by genCachedConstructionData, which 
     53         * is an acceptible method of failure (and is unlikely anyway). */ 
    5454         
    5555        // Get all sub-widgets 
     
    7575        int lenUsed = 0; 
    7676        if (data.length < rows + cols) {    // data error; use defaults 
    77             colW = colWMin.dup
    78             rowH = rowHMin.dup
     77            col.dupMin
     78            row.dupMin
    7979        } else {                            // sufficient data 
    8080            lenUsed = rows+cols; 
    81             colW = data[0..cols]; 
    82             rowH = data[cols..lenUsed]; 
    83              
    84             // Check row sizes are valid: 
    85             //NOTE: this could be made optional 
    86             //NOTE: could also check non-resizable sizes are not too large 
    87             foreach (i, ref w; colW) 
    88                 if (w < colWMin[i]) w = colWMin[i]; 
    89             foreach (i, ref h; rowH) 
    90                 if (h < rowHMin[i]) h = rowHMin[i]; 
    91         } 
    92          
    93         genCachedMutableData; 
    94         w = colW[$-1] + colX[$-1]; 
    95         h = rowY[$-1] + rowH[$-1]; 
     81            col.setCheck (data[0..cols]); 
     82            row.setCheck (data[cols..lenUsed]); 
     83        } 
     84         
     85         
     86        // Generate cached mutable data 
     87        // Calculate column and row locations: 
     88        w = col.genPositions; 
     89        h = row.genPositions; 
     90         
     91        // Tell subwidgets their new sizes. Positions are given by a later call to setPosition. 
     92        foreach (i,widget; subWidgets) 
     93            // Resizing direction is arbitrarily set to "high direction": 
     94            widget.setSize (col.width[i % cols], row.width[i / cols], true, true); 
    9695         
    9796        return data[lenUsed..$]; 
     
    114113            ret ~= widget.getMutableData; 
    115114         
    116         ret ~= colW ~ rowH
     115        ret ~= col.width ~ row.width
    117116        return ret; 
    118117    } 
    119118     
    120119    bool isWSizable () { 
    121         return (sizableCols.length != 0); 
    122     } 
    123      
     120        return col.firstSizable >= 0; 
     121    } 
    124122    bool isHSizable () { 
    125         return (sizableRows.length != 0)
     123        return row.firstSizable >= 0
    126124    } 
    127125     
     
    132130    } 
    133131     
    134     void setSize (int nw, int nh) { 
    135         // Step 1: calculate the row/column sizes. 
    136         w += adjustCellSizes (colW, colWMin, sizableCols, nw - w, true); 
    137         h += adjustCellSizes (rowH, rowHMin, sizableRows, nh - h, true); 
    138          
    139         // Step 2: calculate the row/column offsets (positions) and set the sub-widget's sizes. 
    140         genCachedMutableData; 
    141          
    142         // Step 3: position needs to be set 
    143         // Currently this happens by specifying that setPosition should be run after setSize. 
     132    void setSize (int nw, int nh, bool wHigh, bool hHigh) { 
     133        debug scope (failure) { 
     134            char[128] tmp; 
     135                logger.trace ("setSize failed: hHigh = " ~ (hHigh ? "true" : "false")); 
     136                logger.trace (logger.format (tmp, "rows to resize: {}, {}", row.firstSizable, row.lastSizable)); 
     137        } 
     138        // Optimisation (could easily be called with same sizes if a parent layout widget is 
     139        // resized, since many columns/rows may not be resized). 
     140        if (nw == w && nh == h) return; 
     141         
     142        // calculate the row/column sizes (and new positions) 
     143        if (wHigh) 
     144            w += col.adjustCellSizes (nw - w, col.lastSizable, -1); 
     145        else 
     146            w += col.adjustCellSizes (nw - w, col.firstSizable, 1); 
     147        if (hHigh) 
     148            h += row.adjustCellSizes (nh - h, row.lastSizable, -1); 
     149        else 
     150            h += row.adjustCellSizes (nh - h, row.firstSizable, 1); 
     151         
     152        // set the sub-widget's sizes & positions 
     153        setSubWidgetSP (wHigh, hHigh); 
    144154    } 
    145155     
     
    149159         
    150160        foreach (i,widget; subWidgets) 
    151             widget.setPosition (x + colX[i % cols], y + rowY[i / cols]); 
     161            widget.setPosition (x + col.pos[i % cols], y + row.pos[i / cols]); 
    152162    } 
    153163     
     
    155165    // Find the relevant widget. 
    156166    IWidget getWidget (int cx, int cy) { 
    157         int lx = cx - x, ly = cy - y;       // use coords relative to this widget 
    158          
    159         // Find the column 
    160         int i = cols - 1;                   // starting from right... 
    161         while (lx < colX[i]) {              // decrement while left of this column 
    162             debug assert (i > 0, "getWidget: left of first column");  // should be impossible 
    163             --i; 
    164         }                                   // now (lx >= colX[i]) 
    165         if (lx >= colX[i] + colW[i]) return this;   // between columns 
    166          
    167         // Find the row; 
    168         int j = rows - 1; 
    169         while (ly < rowY[j]) { 
    170             debug assert (j > 0, "getWidget: above first row");   // should be impossible 
    171             --j; 
    172         } 
    173         if (ly >= rowY[j] + rowH[j]) return this; 
    174          
    175         // Now we know it's in widget (i,j)'s cell (but the widget may not take up the whole cell) 
    176         lx -= colX[i]; 
    177         ly -= rowY[j]; 
    178         IWidget widg = subWidgets[i + j*cols]; 
    179         widg.getCurrentSize (i,j); 
    180         if (lx < i && ly < j) 
    181             return widg.getWidget (cx, cy); 
    182         return this;    // wasn't in cell 
     167        debug scope (failure) 
     168                logger.warn ("getWidget: failure"); 
     169        // Find row/column: 
     170        myDiff i = col.getCell (cx - x); 
     171        myDiff j = row.getCell (cy - y); 
     172        if (i < 0 || j < 0) // on a space between widgets 
     173            return this; 
     174         
     175        // On a subwidget; recurse call: 
     176        return subWidgets[i + j*cols].getWidget (cx, cy); 
    183177    } 
    184178     
     
    192186            * resizeRow is non-negative). */ 
    193187             
    194             // Find the column 
    195             if (sizableCols.length != 0) { 
    196                 int l = cx - x;                 // use relative coords 
    197                 size_t i = cols - 1;            // index, from right 
    198                 while (l < colX[i]) {           // decrement while left of this column 
    199                     debug assert (i > 0, "clickEvent: left of first column"); 
    200                     --i; 
    201                 }                               // now (l >= colX[resizeCol]) 
    202                 if (l < colX[i] + colW[i]) i = -1;  // on a sub-widget 
    203                  
    204                 // Set resizeColsL / resizeColsH 
    205                 // Want to find j such that [0..j],[j..$] divide sizableCols about i: 
    206                 size_t j = 0; 
    207                 while (j < sizableCols.length && sizableCols[j] <= i) ++j; 
    208                  
    209                 resizeColsL = sizableCols[0..j]; 
    210                 resizeColsH = sizableCols[j..$]; 
    211                  
    212                 // Cannot resize if either list is empty. resizeCallback checks the length of L, 
    213                 // but to save it checking R too, we set L's length zero if R's is. 
    214                 if (resizeColsH.length == 0) 
    215                     resizeColsL = null; 
    216             } 
    217              
    218             // Find the row 
    219             if (sizableRows.length != 0) { 
    220                 int l = cy - y; 
    221                 size_t i = rows - 1; 
    222                 while (l < rowY[i]) { 
    223                     debug assert (i > 0, "clickEvent: above first row"); 
    224                     --i; 
    225                 } 
    226                 if (l < rowY[i] + rowH[i]) i = -1; 
    227                  
    228                 size_t j = 0; 
    229                 while (j < sizableRows.length && sizableRows[j] <= i) ++j; 
    230                  
    231                 resizeRowsL = sizableRows[0..j]; 
    232                 resizeRowsH = sizableRows[j..$]; 
    233                  
    234                 if (resizeRowsH.length == 0) 
    235                     resizeRowsL = null; 
    236             } 
    237              
    238             if (resizeColsL is null && resizeRowsL is null) 
    239                 return;     // no resizing to do 
     188            // find col/row's resizeD & resizeU 
     189            if (col.findResize (cx - x) && row.findResize (cy - y)) 
     190                return;     // unable to resize 
    240191             
    241192            dragX = cx; 
     
    259210     * (i.e. to produce cached data calculated from construction data). */ 
    260211    void genCachedConstructionData () { 
     212        col.spacing = row.spacing = window.renderer.layoutSpacing; 
     213         
    261214        // Calculate the minimal column and row sizes: 
    262         colWMin = new int[cols];    // set length, making sure the arrays are initialised to zero 
    263         rowHMin = new int[rows]; 
     215        // set length, making sure the arrays are initialised to zero: 
     216        col.minWidth = new int[cols]; 
     217        row.minWidth = new int[rows]; 
    264218        int ww, wh;     // sub-widget minimal sizes 
    265219        foreach (i,widget; subWidgets) { 
     
    267221             
    268222            // Increase dimensions if current minimal size is larger: 
    269             uint n = i % cols;      // column 
    270             if (colWMin[n] < ww) colWMin[n] = ww; 
    271             n = i / cols;           // row 
    272             if (rowHMin[n] < wh) rowHMin[n] = wh; 
     223            myIt n = i % cols; // column 
     224            if (col.minWidth[n] < ww) col.minWidth[n] = ww; 
     225            n = i / cols;      // row 
     226            if (row.minWidth[n] < wh) row.minWidth[n] = wh; 
    273227        } 
    274228         
    275229         
    276230        // Calculate the overall minimal size, starting with the spacing: 
    277         mh = window.renderer.layoutSpacing;     // use mh temporarily 
     231        mh = window.renderer.layoutSpacing;    // use mh temporarily 
    278232        mw = mh * (cols - 1); 
    279233        mh *= (rows - 1); 
    280234         
    281         foreach (x; colWMin)            // add the column/row's dimensions 
     235        foreach (x; col.minWidth)      // add the column/row's dimensions 
    282236            mw += x; 
    283         foreach (x; rowHMin
     237        foreach (x; row.minWidth
    284238            mh += x; 
    285239         
    286240         
    287241        // Find which cols/rows are resizable: 
    288         sizableCols = sizableRows = null;   // reset; we're about to concatenate to them 
     242        // reset: 
     243        col.sizable = new bool[cols]; 
     244        row.sizable = new bool[rows]; 
     245        col.firstSizable = row.firstSizable = -1; 
    289246         
    290247        forCols: 
    291         for (uint i = 0; i < cols; ++i) {                       // for each column 
    292             for (uint j = 0; j < subWidgets.length; j += cols)  // for each row 
    293                 if (!subWidgets[i+j].isWSizable)        // column not resizable 
    294                     continue forCols;                   // continue the outer for loop 
     248        for (myIt i = 0; i < cols; ++i) {              // for each column 
     249            for (myIt j = 0; j < subWidgets.length; j += cols) // for each row 
     250                if (!subWidgets[i+j].isWSizable)   // column not resizable 
     251                    continue forCols;          // continue the outer for loop 
    295252                 
    296253            // column is resizable if we get to here 
    297             sizableCols ~= i; 
     254            col.sizable[i] = true; 
     255            if (col.firstSizable < 0) 
     256                col.firstSizable = i; 
     257            col.lastSizable = i; 
    298258        } 
    299259         
    300260        forRows: 
    301         for (uint i = 0; i < subWidgets.length; i += cols) {    // for each row 
    302             for (uint j = 0; j < cols; ++j)                     // for each column 
     261        for (myIt i = 0; i < subWidgets.length; i += cols) {   // for each row 
     262            for (myIt j = 0; j < cols; ++j)            // for each column 
    303263                if (!subWidgets[i+j].isHSizable) 
    304264                    continue forRows; 
    305265             
    306             sizableRows ~= i / cols; 
    307         } 
    308     } 
    309      
    310     /* Calculations which need to be run whenever resizing occurs (or deeper alterations) 
    311      * (i.e. to produce cached data calculated from construction and mutable data). */ 
    312     void genCachedMutableData () { 
    313         // Calculate column and row locations: 
    314         colX.length = cols; 
    315         rowY.length = rows; 
    316         int spacing = window.renderer.layoutSpacing; 
    317          
    318         int cum = 0; 
    319         foreach (i, x; rowH) { 
    320             rowY[i] = cum; 
    321             cum += x + spacing; 
    322         } 
    323          
    324         cum = 0; 
    325         foreach (i, x; colW) { 
    326             colX[i] = cum; 
    327             cum += x + spacing; 
    328         } 
    329          
    330         // Tell subwidgets their new sizes: 
    331         foreach (i,widget; subWidgets) 
    332             widget.setSize (colW[i % cols], rowH[i / cols]); 
     266            row.lastSizable = i / cols; 
     267            row.sizable[row.lastSizable] = true; 
     268            if (row.firstSizable < 0) 
     269                row.firstSizable = row.lastSizable; 
     270        } 
     271    } 
     272     
     273    // set sub-widgets size & position (done after resizing widget or rows/columns) 
     274    void setSubWidgetSP (bool wH, bool hH) { 
     275        for (myIt i = 0; i < cols; ++i) 
     276            for (myIt j = 0; j < rows; ++j) 
     277        { 
     278            IWidget widget = subWidgets[i + cols*j]; 
     279            widget.setSize (col.width[i], row.width[j], wH, hH); 
     280            widget.setPosition (x + col.pos[i], y + row.pos[j]); 
     281        } 
    333282    } 
    334283    //END Cache calculation functions 
    335284     
    336     /* Adjust the total size of rows/columns (including spacing) by diff. 
    337      * 
    338      * Params: 
    339      *  cellD       = current sizes; is adjusted by the function to new sizes 
    340      *  cellDMin    = minimal sizes (see colWMin/rowHMin) 
    341      *  sizableCells= List of indexes in cellD for cells which are resizable 
    342      *  diff        = amount to increase/decrease the total size 
    343      *  startHigh   = if true, start resizing from the tail end of cellD, instead of the beginning 
    344      * 
    345      * Returns: 
    346      *  The amount adjusted. This may be larger than diff, since cellD is clamped by cellDMin. 
    347      */ 
    348     int adjustCellSizes (ref int[] cellD, ref int[] cellDMin, ref size_t[] sizableCells, int diff, bool startHigh) 
    349     in {// Could occur if adjust isn't called first, but this would be a code error: 
    350         assert (cellD !is null, "adjustCellSizes: cellD is null"); 
    351     } body { 
    352         // Cannot resize if no cells are sizable: 
    353         if (sizableCells.length == 0) return 0; 
    354          
    355         size_t si = (startHigh ? sizableCells.length-1 : 0);    // starting index of sizableCells 
    356          
    357         // FIXME: could do with an overhaul 
    358         if (diff > 0) {         // increase size of first resizable cell 
    359             cellD[sizableCells[si]] += diff; 
    360             return diff; 
    361         } 
    362         else if (diff < 0) {    // decrease 
    363             size_t sc = (startHigh ? -1 : 1);   // amount to iterate 
    364             size_t ci;          // index in cellD 
    365             int rd = diff;      // running diff 
    366             while (true) { 
    367                 ci = sizableCells[si]; 
    368                  
    369