Changeset 97:30470bc19ca4

Show
Ignore:
Timestamp:
11/10/08 11:44:44 (2 months ago)
Author:
Diggory Hardy <diggory.hardy@gmail.com>
branch:
default
Message:

Floating widgets now work nicely: customizable borders added, resizing, moving.

gl.basic abstraction module removed (seemed pointless).
Some changes to SimpleRenderer? (largely to accomodate floating widgets).

Files:

Legend:

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

    r95 r97  
    99Bugs: 
    1010Sometimes nothing is drawn until a resize, and fonts are blocks. Cause: init-stages always appear to get divided between two threads. If Inpt, Font and SWnd are called by the main thread and not a sub-thread, the bug doesn't occur. A temporary fix is just to set maxThreads=1. A redesign of threading init stages could solve this, but doesn't seem worth the effort right now. 
     11Drawing of floating widgets is not restricted to the floating area; see SimpleRenderer.restrict(). 
    1112 
    1213 
  • data/conf/gui.mtt

    r96 r97  
    1515<WidgetData|optVal={0:[0x6030]}> 
    1616<WidgetData|optSep={0:[0x21, 0xff],1:["="]}> 
    17 <WidgetData|floating={0:[0x8200],1:["text","button"]}> 
     17<WidgetData|floating={0:[0x8200,1,6,14],1:["text","button","blank"]}> 
    1818<WidgetData|text={0:[0x21,0xE0E000],1:["Floating text"]}> 
     19<WDims|root=[6,590,196,6,580,6]> 
     20<WDims|content=[590,190,364,18]> 
     21<WDims|floating=[25,21,103,27,437,23,74,74,217,90,123,100]> 
    1922{Basic} 
    2023<WidgetData|root={0:[0x21,0x90D970],1:["A string!"]}> 
  • mde/font/FontTexture.d

    r96 r97  
    218218        } 
    219219         
     220        glDisable(GL_TEXTURE_2D); 
    220221        glDisable(GL_BLEND); 
    221222    } 
     
    322323        glEnd (); 
    323324         
     325        glDisable(GL_TEXTURE_2D); 
    324326        glDisable(GL_BLEND); 
    325327    } 
  • mde/font/font.d

    r89 r97  
    2828 
    2929import derelict.freetype.ft; 
    30 import derelict.opengl.gl; 
    3130 
    3231import mde.file.deserialize; 
  • mde/gui/WidgetManager.d

    r96 r97  
    128128        h = cast(wdim) nh; 
    129129         
    130         debug logger.trace ("Resize to: {},{}", nw, nh); 
    131130        if (w < mw || h < mh) 
    132131            logger.warn ("Minimal dimensions ({},{}) not met: ({},{}), but I cannot resize myself!",mw,mh,w,h); 
  • mde/gui/renderer/IRenderer.d

    r95 r97  
    2929interface IRenderer 
    3030{ 
    31     //BEGIN Get dimensions 
    32     /// A container for the dimensions 
    33     struct BorderDimensions { 
    34         /// The dimensions: left, top, right, bottom 
    35         wdim l, t, r, b; 
     31    //BEGIN Types 
     32    /** For floating widget borders. Like TextAdapter above, could be more flexible. */ 
     33    struct Border { 
     34        /** Border type. 
     35         *  
     36         * No border, small non-functional border, movement only border, resize only border, or 
     37         * full border. */ 
     38        enum BTYPE : int { 
     39            NONE = 0, SMALL = 1, LARGE = 2, MOVE = 4, RESIZE = 8, BOTH = MOVE | RESIZE 
     40        } 
     41        /** Which edges of a window are being resized. 
     42         * 
     43         * E.g. X1 == left, X2 | Y1 == right and top. */ 
     44        enum RESIZE : int { 
     45            NONE = 0x0, X1 = 0x1, X2 = 0x2, Y1 = 0x4, Y2 = 0x8 
     46        } 
    3647         
    37         void opAddAssign (BorderDimensions d) { 
    38             l += d.l; 
    39             t += d.t; 
    40             r += d.r; 
    41             b += d.b; 
     48        wdim x1,x2;     /// First and second (lef & right) horizontal borders 
     49        wdim y1,y2;     /// First and second vertical borders 
     50        RESIZE capability;      /// Which resizes are possible. 
     51         
     52        void opAddAssign (Border d) { 
     53            x1 += d.x1; 
     54            x2 += d.x2; 
     55            y1 += d.y1; 
     56            y2 += d.y2; 
     57        } 
     58         
     59        /** Used to tell if a click on a window's border is for resizing or moving. 
     60         * 
     61         * Depends on setSizable's parameters. 
     62         * 
     63         * Params: 
     64         *   cx = 
     65         *   cy = click coordinates relative to window border 
     66         *   w  = 
     67         *   h  = window size 
     68         * 
     69         * Returns: 
     70         *   RESIZE = Describes whether the click resizes or moves the widget. */ 
     71        RESIZE getResize (wdim cx, wdim cy, wdim w, wdim h) { 
     72            RESIZE r = RESIZE.NONE; 
     73            if (cx + cy < x1 + y1) 
     74                r = RESIZE.X1 | RESIZE.Y1; 
     75            else if (cx + h - cy < x1 + y2) 
     76                r = RESIZE.X1 | RESIZE.Y2; 
     77            else if (w - cx + cy < x2 + y1) 
     78                r = RESIZE.X2 | RESIZE.Y1; 
     79            else if (w - cx + h - cy < x2 + y2) 
     80                r = RESIZE.X2 | RESIZE.Y2; 
     81            return r & capability; 
    4282        } 
    4383    } 
    44      
    45     /** Use to set and reset these parameters, and to get the border size (which may depend on 
    46      * them). */ 
    47     BorderDimensions setSizable (bool wSizable, bool hSizable); 
    48      
    49     /// Which edges of a window are being resized 
    50     enum RESIZE_TYPE : ubyte { 
    51         NONE = 0x0, L = 0x1, R = 0x2, T = 0x4, B = 0x8 
    52     } 
    53      
    54     /** Used to tell if a click on a window's border is for resizing or moving. 
    55     * 
    56     * Depends on setSizable's parameters. 
    57     * 
    58     * Params: 
    59     *   cx = 
    60     *   cy = click coordinates relative to window border 
    61     *   w  = 
    62     *   h  = window size 
    63     * 
    64     * Returns: 
    65     *   RESIZE_TYPE = NONE for a move, an or'd combination of L/R/T/B for resizing. 
    66     */ 
    67     RESIZE_TYPE getResizeType (wdim cx, wdim cy, wdim w, wdim h); 
    68      
    69     /** Return the renderer's between-widget spacing (for layout widgets). */ 
    70     wdim layoutSpacing (); 
    71     //END Get dimensions 
    72      
    73     //BEGIN draw routines 
    74     /** Restrict following draw operations to given box. 
    75      * 
    76      * Restrict "pushes" a restriction onto a stack; relax must be called afterwards to "pop" the 
    77      * restriction. */ 
    78     void restrict (wdim x, wdim y, wdim w, wdim h); 
    79     void relax ();      /// ditto 
    80      
    81     /** Draw a window border plus background. */ 
    82     void drawWindow (wdim x, wdim y, wdim w, wdim h); 
    83      
    84     /** Draws a widget background. Usually doesn't do anything since backgrounds are transparent. */ 
    85     void drawWidgetBack (wdim x, wdim y, wdim w, wdim h); 
    86      
    87     /** Draws a blank widget (temporary) */ 
    88     void drawBlank (wdim x, wdim y, wdim w, wdim h); 
    89      
    90     /** Draws a button frame, in if pushed == true. */ 
    91     void drawButton (wdim x, wdim y, wdim w, wdim h, bool pushed); 
    92      
    93     /** Toggle buttons. 
    94      * 
    95      * These have a fixed size which getToggleSize returns. */ 
    96     wdimPair getToggleSize (); 
    97     void drawToggle (wdim x, wdim y, bool state, bool pushed);  /// ditto 
    98      
    99     /** Get a TextAdapter to draw some text. 
    100      * 
    101      * If no colour is passes, a default is used (white). */ 
    102     TextAdapter getAdapter (char[] text, int colour = 0xFFFFFF); 
    10384     
    10485    /** For drawing text - one instance per string. 
     
    127108        FontStyle font; 
    128109    } 
    129     //END draw routines 
     110    //END Types 
     111     
     112    //BEGIN Methods 
     113    /** Returns a Border containing dimensions. 
     114     * 
     115     * The dimensions may depend on whether the widget is resizable (horizontally and vertically). 
     116     */ 
     117    Border getBorder (Border.BTYPE type, bool wSizable, bool hSizable); 
     118     
     119    /** Return the renderer's between-widget spacing (for layout widgets). */ 
     120    wdim layoutSpacing (); 
     121     
     122    /** Restrict following draw operations to given box. 
     123     * 
     124     * Restrict "pushes" a restriction onto a stack; relax must be called afterwards to "pop" the 
     125     * restriction. */ 
     126    void restrict (wdim x, wdim y, wdim w, wdim h); 
     127    void relax ();      /// ditto 
     128     
     129    /** Draw a window border plus background. */ 
     130    void drawWindow (Border* border, wdim x, wdim y, wdim w, wdim h); 
     131     
     132    /** Draws a widget background. Usually doesn't do anything since backgrounds are transparent. */ 
     133    void drawWidgetBack (wdim x, wdim y, wdim w, wdim h); 
     134     
     135    /** Draws a blank widget (temporary) */ 
     136    void drawBlank (wdim x, wdim y, wdim w, wdim h); 
     137     
     138    /** Draws a button frame, in if pushed == true. */ 
     139    void drawButton (wdim x, wdim y, wdim w, wdim h, bool pushed); 
     140     
     141    /** Toggle buttons. 
     142     * 
     143     * These have a fixed size which getToggleSize returns. */ 
     144    wdimPair getToggleSize (); 
     145    void drawToggle (wdim x, wdim y, bool state, bool pushed);  /// ditto 
     146     
     147    /** Get a TextAdapter to draw some text. 
     148     * 
     149     * If no colour is passes, a default is used (white). */ 
     150    TextAdapter getAdapter (char[] text, int colour = 0xFFFFFF); 
     151    //END Methods 
    130152} 
  • mde/gui/renderer/SimpleRenderer.d

    r95 r97  
    1919import mde.gui.renderer.IRenderer; 
    2020 
    21 import gl = mde.gl.basic
     21import derelict.opengl.gl
    2222import mde.font.font; 
    2323 
     
    3535    } 
    3636     
    37     BorderDimensions setSizable (bool wS, bool hS) { 
    38         wSizable = wS; 
    39         hSizable = hS; 
    40          
    41         // Set the border size based on the above 
     37    alias Border.BTYPE BTYPE; 
     38    Border getBorder (BTYPE type, bool wS, bool hS) { 
     39        Border border; 
    4240        with (border) { 
    43             l = r = t = b = 14; 
     41            if (type & BTYPE.RESIZE) { 
     42                if (wS) capability  = RESIZE.X1 | RESIZE.X2; 
     43                if (hS) capability |= RESIZE.Y1 | RESIZE.Y2; 
     44            } 
     45            if (type & BTYPE.LARGE) { 
     46                y1 = 12; 
     47                y2 = 6; 
     48            } 
     49            else if (type & BTYPE.SMALL) 
     50                y1 = y2 = 4; 
     51            x1 = x2 = y2; 
    4452        } 
    45         with (resize) { 
    46             if (wSizable) 
    47                 l = r = 6; 
    48             else 
    49                 l = r = 0; 
    50             if (hSizable) { 
    51                 t = b = 6; 
    52             } else 
    53                 t = b = 0; 
    54         } 
    55         border += resize; 
    5653        return border; 
    57     } 
    58      
    59     RESIZE_TYPE getResizeType (wdim cx, wdim cy, wdim w, wdim h) { 
    60         RESIZE_TYPE resizeType = RESIZE_TYPE.NONE; 
    61         if (cx < resize.l || cx >= w - resize.r || 
    62             cy < resize.t || cy >= h - resize.b) { // window is being resized 
    63                 /* check for resizes (different to above; use whole border giving larger area for 
    64                 * diagonal resizes). */ 
    65              
    66             if (wSizable) { 
    67                 if (cx < border.l) 
    68                     resizeType = RESIZE_TYPE.L; 
    69                 else if (cx >= w - border.r) 
    70                     resizeType = RESIZE_TYPE.R; 
    71             } 
    72             if (hSizable) { 
    73                 if (cy < border.t) 
    74                     resizeType |= RESIZE_TYPE.T; 
    75                 else if (cy >= h - border.b) 
    76                     resizeType |= RESIZE_TYPE.B; 
    77             } 
    78         } 
    79         return resizeType; 
    8054    } 
    8155     
     
    8963    void relax () {} 
    9064     
    91     void drawWindow (wdim x, wdim y, wdim w, wdim h) { 
    92         gl.setColor (0f, 0f, .7f); 
    93         gl.drawBox (x,y, w,h); 
     65    void drawWindow (Border* border, wdim x, wdim y, wdim w, wdim h) { 
     66        glColor3f (0f, 0f, .8f); 
     67        glRecti(x, y+h, x+w, y); 
    9468         
    95         gl.setColor (0f, 0f, 1f); 
    96         gl.drawBox (x+resize.l, y+resize.t, w-resize.l-resize.r, h-resize.t-resize.b); 
     69        if (border.capability != 0) { 
     70            glColor3f (0f, 0f, .7f); 
     71            glBegin (GL_TRIANGLES); 
     72            wdim t = border.x1 + border.y1; 
     73            glVertex2i (x, y); 
     74            glVertex2i (x+t, y); 
     75            glVertex2i (x, y+t); 
     76             
     77            t = border.x2 + border.y1; 
     78            glVertex2i (x+w, y); 
     79            glVertex2i (x+w, y+t); 
     80            glVertex2i (x+w-t, y); 
     81             
     82            t = border.x2 + border.y2; 
     83            glVertex2i (x+w, y+h); 
     84            glVertex2i (x+w-t, y+h); 
     85            glVertex2i (x+w, y+h-t); 
     86             
     87            t = border.x1 + border.y2; 
     88            glVertex2i (x, y+h); 
     89            glVertex2i (x, y+h-t); 
     90            glVertex2i (x+t, y+h); 
     91            glEnd (); 
     92        } 
    9793         
    98         gl.setColor (.3f, .3f, .3f); 
    99         gl.drawBox (x+border.l, y+border.t, w-border.l-border.r, h-border.t-border.b); 
     94        glColor3f (0f, 0f, 0f); 
     95        glRecti(x+border.x1, y+h-border.y2, x+w-border.x2, y+border.y1); 
    10096    } 
    10197 
    10298    void drawWidgetBack (wdim x, wdim y, wdim w, wdim h) { 
    10399        debug { 
    104             gl.setColor (0f, .2f, .2f); 
    105             gl.drawBox (x,y, w,h); 
     100            glColor3f (0f, .2f, .2f); 
     101            glRecti (x,y+h, x+w,y); 
    106102        } 
    107103    } 
    108104     
    109105    void drawBlank (wdim x, wdim y, wdim w, wdim h) { 
    110         gl.setColor (.4f, .4f, .4f); 
    111         gl.drawBox (x,y, w,h); 
     106        glColor3f (.4f, .4f, .4f); 
     107        glRecti(x, y+h, x+w, y); 
    112108    } 
    113109     
    114110    void drawButton (wdim x, wdim y, wdim w, wdim h, bool pushed) { 
    115111        if (pushed) 
    116             gl.setColor (1f, 0f, 1f); 
     112            glColor3f (1f, 0f, 1f); 
    117113        else 
    118             gl.setColor (.6f, 0f, .6f); 
    119         gl.drawBox (x,y, w,h); 
     114            glColor3f (.6f, 0f, .6f); 
     115        glRecti(x, y+h, x+w, y); 
    120116    } 
    121117     
     
    129125        float c = pushed ? .7f : .5f; 
    130126        if (state) 
    131             gl.setColor (0f, c, 0f); 
     127            glColor3f (0f, c, 0f); 
    132128        else 
    133             gl.setColor (c, 0f, 0f); 
    134         gl.drawBox (x+2,y+2, 12,12); 
     129            glColor3f (c, 0f, 0f); 
     130        glRecti (x+2,y+14, x+14,y+2); 
    135131    } 
    136132     
     
    143139     
    144140protected: 
    145     bool wSizable, hSizable; 
    146     BorderDimensions border; 
    147     BorderDimensions resize; 
    148141    FontStyle defaultFont; 
    149142} 
  • mde/gui/widget/Floating.d

    r96 r97  
    1919import mde.gui.widget.Widget; 
    2020import mde.gui.exception; 
    21 /+import mde.gui.widget.createWidget; 
    22  
    23 import mde.gui.IGui; 
    24 import mde.gui.renderer.createRenderer; 
    25  
    26 import mt = mde.mergetag.DataSet; 
    27 import mde.mergetag.parse.parseTo : parseTo; 
    28 import mde.mergetag.parse.parseFrom : parseFrom; 
    29 +/ 
     21 
    3022import tango.util.log.Log : Log, Logger; 
    3123 
     
    4840{ 
    4941    this (IWidgetManager mgr, widgetID id, WidgetData data) { 
    50         if (data.ints.length != 1
     42        if (data.ints.length != 1 + data.strings.length
    5143            throw new WidgetDataException (this); 
    5244         
    5345        subWidgets.length = data.strings.length;        // widgets created from string data 
    5446        sWOrder.length = subWidgets.length; 
     47        sWData.length = subWidgets.length; 
    5548        foreach (i,s; data.strings) { 
    5649            subWidgets[i] = mgr.makeWidget (s); 
    5750            sWOrder[i] = i; 
    58         } 
    59          
    60         sWCoords = mgr.dimData (id); 
    61         if (sWCoords.length != subWidgets.length * 2) { 
    62             // don't bother logging a warning; correct data will be saved anyway 
    63             sWCoords.length = subWidgets.length * 2;    // maybe some data kept 
     51            sWData[i].borderType = cast(BTYPE) data.ints[i+1]; 
     52        } 
     53         
     54        wdim[] dd = mgr.dimData (id); 
     55        if (dd.length == subWidgets.length * 4) { 
     56            foreach (i, ref d; sWData) 
     57                (&d.x)[0..4] = dd[i*4..i*4+4]; 
    6458        } 
    6559         
     
    6761    } 
    6862     
     63    void finalize () { 
     64        foreach (i, ref d; sWData) with (d) { 
     65            auto widg = subWidgets[i]; 
     66            d.border = mgr.renderer.getBorder (borderType, widg.isWSizable, widg.isHSizable); 
     67            mw = widg.minWidth  + border.x1 + border.x2; 
     68            mh = widg.minHeight + border.y1 + border.y2; 
     69            if (w < mw) w = mw; 
     70            if (h < mh) h = mh; 
     71            widg.setWidth  (w - border.x1 - border.x2, -1); 
     72            widg.setHeight (h - border.y1 - border.y2, -1); 
     73        } 
     74    } 
     75     
    6976    bool saveChanges () { 
    70         foreach (widget; subWidgets) 
    71             widget.saveChanges (); 
    72          
    73         mgr.setDimData (id, sWCoords);  // save positions 
     77        wdim[] dd = new wdim[sWData.length*4]; 
     78        foreach (i, ref d; sWData) { 
     79            subWidgets[i].saveChanges (); 
     80            dd[4*i..4*i+4] = (&d.x)[0..4]; 
     81        } 
     82         
     83        mgr.setDimData (id, dd);  // save positions 
    7484        return true; 
    7585    } 
     
    7888        w = nw; 
    7989        // check all floating widgets are visible 
    80         foreach (i, widg; subWidgets) { 
    81             wdim d; 
    82             if (sWCoords[i] + (d = widg.width) > w) { 
    83                 if (d > w) 
    84                     sWCoords[i] = 0; 
    85                 else 
    86                     sWCoords[i] = w - d; 
    87             } 
     90        foreach (i, ref d; sWData) with (d) { 
     91            if (x + w > this.w) 
     92                x = (w > this.w) ? 0 : this.w - w; 
    8893        } 
    8994    } 
    9095    void setHeight (wdim nh, int) { 
    9196        h = nh; 
    92         foreach (i, widg; subWidgets) { 
    93             wdim d; 
    94             size_t n = i + subWidgets.length; 
    95             if (sWCoords[n] + (d = widg.height) > h) { 
    96                 if (d > h) 
    97                     sWCoords[n] = 0; 
    98                 else 
    99                     sWCoords[n] = h - d; 
    100             } 
     97        foreach (i, ref d; sWData) with (d) { 
     98            if (y + h > this.h) 
     99                y = (h > this.h) ? 0 : this.h - h; 
    101100        } 
    102101    } 
     
    105104    bool isHSizable () {    return true;    } 
    106105     
    107     void setPosition (wdim x, wdim y) { 
    108         super.setPosition (x,y); 
     106    void setPosition (wdim nx, wdim ny) { 
     107        x = nx; 
     108        y = ny; 
    109109         
    110110        size_t n = subWidgets.length; 
    111         foreach (i,widg; subWidgets
    112             widg.setPosition (x+sWCoords[i], y+sWCoords[i+n]); 
     111        foreach (i, ref d; sWData
     112            subWidgets[i].setPosition (x + d.x + d.border.x1, y + d.y + d.border.y1); 
    113113    } 
    114114     
     
    117117         
    118118        mgr.renderer.restrict (x,y, w,h); 
    119          
    120119        foreach (i; sWOrder) 
    121             subWidgets[i].draw; 
     120            with (sWData[i]) { 
     121                mgr.renderer.drawWindow (&border, this.x + x, this.y + y, w, h); 
     122                subWidgets[i].draw; 
     123            } 
     124        mgr.renderer.relax; 
    122125    } 
    123126     
    124127    IChildWidget getWidget (wdim cx, wdim cy) { 
     128        debug scope (failure) 
     129            logger.warn ("getWidget: failure; values: click, pos, width - {}, {}, {} - {}, {}, {}", cx, x, w, cy, y, h); 
    125130        debug assert (cx >= x && cx < x + w && cy >= y && cy < y + h, "getWidget: not on widget (code error)"); 
    126131         
    127         size_t n = subWidgets.length; 
    128         foreach_reverse (j,i; sWOrder) { 
    129             wdim lx = cx - (x + sWCoords[i  ]); 
    130             wdim ly = cy - (y + sWCoords[i+n]); 
    131             if (lx >= 0 && lx < subWidgets[i].width && 
    132                 ly >= 0 && ly < subWidgets[i].height) 
     132        foreach_reverse (j,i; sWOrder) with (sWData[i]) { 
     133            wdim lx = cx - (this.x + x); 
     134            wdim ly = cy - (this.y + y); 
     135            if (lx >= 0 && lx < w && 
     136                ly >= 0 && ly < h) 
    133137            { 
    134138                sWOrder[j..$-1] = sWOrder[j+1..$].dup; 
    135139                sWOrder[$-1] = i; 
    136140                mgr.requestRedraw; 
    137                 return subWidgets[i]; 
    138             } 
    139         } 
     141                if (lx >= border.x1 && lx < w-border.x2 && 
     142                    ly >= border.y1 && ly < h-border.y2) 
     143                    return subWidgets[i].getWidget (cx,cy); 
     144                event = i; 
     145                return this; 
     146            } 
     147        } 
     148        event = size_t.max; 
    140149        return this;    // no match 
    141150    } 
    142151     
     152    void clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) { 
     153        if (event > subWidgets.length) return; 
     154        if (b == 1 && state == true) { 
     155            active = event; 
     156            with (sWData[active]) { 
     157                resizeType = border.getResize (cx - this.x - x, cy - this.y - y, w,h); 
     158                alias border.RESIZE RESIZE; 
     159                 
     160                if (resizeType != RESIZE.NONE) { 
     161                    // Set x/yDrag (unfortunately these need to be different for each edge) 
     162                    if (resizeType & RESIZE.X1) 
     163                        xDrag = w + cx; 
     164                    else if (resizeType & RESIZE.X2) 
     165                        xDrag = w - cx; 
     166                     
     167                    if (resizeType & RESIZE.Y1) 
     168                        yDrag = h + cy; 
     169                    else if (resizeType & RESIZE.Y2) 
     170                        yDrag = h - cy; 
     171                     
     172                    mgr.addClickCallback (&endCallback); 
     173                    mgr.addMotionCallback (&resizeCallback); 
     174                } else if (borderType & BTYPE.MOVE) {   // window is being moved 
     175                    xDrag = cx - x; 
     176                    yDrag = cy - y; 
     177                     
     178                    mgr.addClickCallback (&endCallback); 
     179                    mgr.addMotionCallback (&moveCallback); 
     180                } 
     181            } 
     182        } 
     183    } 
     184     
    143185protected: 
    144     wdim[] sWCoords;    // coords for subwidgets, relative to this widget: [x1,x2,...,y1,y2,...] 
     186    void moveCallback (wdabs cx, wdabs cy) { 
     187        with (sWData[active]) { 
     188                x = cx-xDrag; 
     189                y = cy-yDrag; 
     190                 
     191                if (x < 0) 
     192                    x = 0; 
     193                else if (x + w > this.w) 
     194                    x = (w > this.w) ? 0 : this.w - w; 
     195                if (y < 0) 
     196                    y = 0; 
     197                else if (y + h > this.h) 
     198                    y = (h > this.h) ? 0 : this.h - h; 
     199                 
     200                subWidgets[active].setPosition (this.x + x + border.x1, this.y + y + border.y1); 
     201        } 
     202        mgr.requestRedraw; 
     203    } 
     204    void resizeCallback (wdabs cx, wdabs cy) { 
     205        with (sWData[active]) { 
     206            if (resizeType & RESIZE.X1) { 
     207                wdim ow = w; 
     208                w = xDrag - cx; 
     209                if (w < mw) w = mw; 
     210                x += ow - w; 
     211                subWidgets[active].setWidth  (w - border.x1 - border.x2, 1); 
     212            } 
     213            else if (resizeType & RESIZE.X2) { 
     214                w = xDrag + cx; 
     215                if (w < mw) w = mw; 
     216                subWidgets[active].setWidth  (w - border.x1 - border.x2, -1); 
     217            } 
     218            if (resizeType & RESIZE.Y1) { 
     219                wdim oh = h; 
     220                h = yDrag - cy; 
     221                if (h < mh) h = mh; 
     222                y += oh - h; 
     223                subWidgets[active].setHeight (h - border.y1 - border.y2, 1); 
     224            } 
     225            else if (resizeType & RESIZE.Y2) { 
     226                h = yDrag + cy; 
     227                if (h < mh) h = mh; 
     228                subWidgets[active].setHeight (h - border.y1 - border.y2, -1); 
     229            } 
     230            // Reposition widget and sub-widgets: 
     231            subWidgets[active].setPosition (this.x + x + border.x1, this.y + y + border.y1); 
     232        } 
     233        mgr.requestRedraw; 
     234    } 
     235    bool endCallback (wdabs, wdabs, ubyte b, bool state) { 
     236        if (b == 1 && state == false) { // end of a move/resize 
     237            mgr.removeCallbacks (cast(void*) this); 
     238            return true;    // we've handled the up-click 
     239        } 
     240        return false;       // we haven't handled it 
     241    } 
     242     
     243    struct SWData {     // NOTE: x,y,w,h must be first elements; search (&d.x) 
     244        wdim x,y;       // position (corner of border) 
     245        wdim w,h;       // size (including border) 
     246        wdim mw,mh; 
     247        BTYPE borderType;       // what type of border to put around the widget 
     248        IRenderer.Border border; 
     249    } 
     250    static assert (SWData.alignof == 4); // assumptions for optimization; search (&d.x) 
     251    SWData[] sWData; 
    145252    size_t[] sWOrder;   // indexes for draw order (top widget at end of list) 
    146253     
    147     /+ 
    148     /** Call after loading is finished to setup the window and confirm that it's valid. 
    149      * 
    150      * Throws: WindowLoadException (or possibly other exceptions). Do not use the instance if an 
    151      * exception occurs! */ 
    152     void finalise (IGui gui) 
    153     in { 
    154         assert (gui !is null, "Window.finalise ("~name~"): gui is null"); 
    155     } body { 
    156         // Check data was loaded: 
    157         if (widgetData is null) 
    158             throw new WindowLoadException ("No widget creation data"); 
    159          
    160         // Save gui and create the renderer: 
    161         gui_ = gui; 
    162         rend = createRenderer (gui.rendererName); 
    163          
    164         // Create the primary widget (and indirectly all sub-widgets), throwing on error: 
    165         // Note: GridLayoutWidget's this relies on rend.layoutSpacing. 
    166         widget = makeWidget (0);        // primary widget always has ID 0. 
    167         // This data is no longer needed by Window, although its sub-arrays may still be used, so 
    168         // let the GC collect what it can: 
    169         widgetData = null; 
    170         widgetStrings = null; 
    171          
    172         // get border sizes: 
    173         border = rend.setSizable (isWSizable, isHSizable);  // depends on widget 
    174          
    175         // Note: this should return an empty array, but we shouldn't make a fuss if it's not empty: 
    176         if ((widget.adjust (mutableData)).length != 0)  // adjust/set size, etc., depends on rend 
    177             logger.warn ("Local widget position data is invalid!"); 
    178         mutableData = null;             // no longer needed 
    179          
    180         widget.getCurrentSize (w,h);    // and get this size 
    181         w += border.l + border.r;       // Adjust for border 
    182         h += border.t + border.b; 
    183          
    184         widgetX = x + border.l;         // widget position 
    185         widgetY = y + border.t;         // must be updated if the window is moved 
    186         widget.setPosition (widgetX, widgetY); 
    187          
    188         // Calculate mw/mh and xw/yh (cached data): 
    189         mw = widget.minWidth  + border.l + border.r; 
    190         mh = widget.minHeight + border.t + border.b; 
    191          
    192         xw = x+w; 
    193         yh = y+h; 
    194     } 
    195     //END Methods for GUI 
    196      
    197     //BEGIN IWindow methods 
    198     /** Get/create a widget by ID. 
    199      * 
    200      * Should $(I only) be called internally and by sub-widgets! */ 
    201     IWidget makeWidget (widgetID i) 
    202     in { 
    203         // widgetData is normally left to be garbage collected after widgets have been created: 
    204         assert (widgetData !is null, "Window.makeWidget ("~name~"): widgetData is null"); 
    205     } body { 
    206         /* Each widget returned should be a unique object; if multiple widgets are requested with 
    207         * the same ID, a new widget is created each time. */ 
    208          
    209         int[]* d = i in widgetData; 
    210         if (d is null) 
    211             throw new WindowLoadException ("Window.makeWidget ("~name~"): Widget not found"); 
    212          
    213         // Throws WidgetDataException (a WindowLoadException) if bad data: 
    214         return createWidget (this, *d); 
    215     } 
    216      
    217     char[] getWidgetString (int i) 
    218     in { 
    219         // widgetStrings is freed at same time as widgetData 
    220         // but widgetData is guaranteed to be read 
    221         assert (widgetData !is null, "getWidgetString called after widget creation finished"); 
    222     } body { 
    223         char[]* p; 
    224         if (widgetStrings is null || 
    225              (p = i in widgetStrings) is null ) 
    226             throw new WindowLoadException ("Needed widgetStrings not set for Window"); 
    227          
    228         return *p; 
    229     } 
    230      
    231     /** Add this widget's data to that to be saved, returning it's widgetID. */ 
    232     widgetID addCreationData (IWidget widget) 
    233     { 
    234         widgetID i; 
    235         if (widgetData is null) 
    236             i = 0; 
    237         else 
    238             i = widgetData.keys[$-1] + 1; 
    239          
    240         /+ Doesn't this have no effect except when getCreationData throws, in which case the data 
    241          + isn't used anyway? I'm sure it was added for a reason... FIXME and below 
    242         widgetData[i] = null;   // Make sure the same ID doesn't get used by a recursive call 
    243         +/ 
    244         widgetData[i] = widget.getCreationData; 
    245          
    246         return i; 
    247     } 
    248      
    249     int addWidgetString (char[] str) 
    250     { 
    251         int i; 
    252         if (widgetStrings is null) 
    253             i = 0; 
    254         else 
    255             i = widgetStrings.keys[$-1] + 1; 
    256          
    257         /+ See above. FIXME 
    258         widgetStrings[i] = null;   // Make sure the same ID doesn't get used by a recursive call 
    259         +/ 
    260         widgetStrings[i] = str; 
    261          
    262         return i; 
    263     } 
    264      
    265     IGui gui () { 
    266         return gui_; 
    267     } 
    268      
    269     void requestRedraw () { 
    270         gui_.requestRedraw; 
    271     } 
    272      
    273     IRenderer renderer () 
    274     in { 
    275         assert (rend !is null, "Window.renderer: rend is null"); 
    276     } body { 
    277         return rend; 
    278     } 
    279     //END IWindow methods 
    280      
    281     //BEGIN IWidget methods 
    282     //FIXME: how many of these methods are actually needed/used? 
    283     int[] adjust (int[]) {          // simply not relevant (never used) 
    284         return []; 
    285     } 
    286     int[] getCreationData () {      // simply not relevant (never used) 
    287         return []; 
    288     } 
    289     int[] getMutableData () {       // simply not relevant (never used) 
    290         return []; 
    291     } 
    292      
    293     bool isWSizable () { 
    294         return widget.isWSizable; 
    295     } 
    296     bool isHSizable () { 
    297         return widget.isHSizable; 
    298     } 
    299      
    300     wdim minWidth () { 
    301         return mw; 
    302     } 
    303     wdim minHeight () { 
    304         return mh; 
    305     } 
    306     void getCurrentSize (out wdim cw, out wdim ch) { 
    307         cw = w; 
    308         ch = h; 
    309     } 
    310      
    311     void setWidth (wdim nw, int dir) { 
    312         if (nw < mw) w = mw;    // clamp 
    313         else w = nw; 
    314         xw = x + w; 
    315         widget.setWidth (w - border.l - border.r, dir); 
    316     } 
    317     void setHeight (wdim nh, int dir) { 
    318         if (nh < mh) h = mh;    // clamp 
    319         else h = nh; 
    320         yh = y + h; 
    321         widget.setHeight (h - border.t - border.b, dir); 
    322     } 
    323      
    324     void setPosition (wdim nx, wdim ny) { 
    325         x = nx; 
    326         y = ny; 
    327          
    328         xw = x+w; 
    329         yh = y+h; 
    330          
    331         widgetX = x + border.l; 
    332         widgetY = y + border.t; 
    333          
    334         widget.setPosition (widgetX, widgetY); 
    335          
    336         gui_.requestRedraw ();  // necessary whenever the window is moved; setPosition is called after resizes and moves 
    337     } 
    338      
    339     IWidget getWidget (wdim cx, wdim cy) { 
    340         if (cx < x || cx >= xw || cy < y || cy >= yh)   // not over window 
    341             return null; 
    342         if (cx >= widgetX && cx < xw-border.r && cy >= widgetY && cy < yh-border.b) 
    343                                                         // over the widget 
    344             return widget.getWidget (cx, cy); 
    345         else                                            // over the window border 
    346             return this; 
    347     } 
    348     void clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) { 
    349         if (b == 1 && state == true) { 
    350             resizeType = rend.getResizeType (cx-x, cy-y, w,h); 
    351              
    352             if (resizeType != RESIZE_TYPE.NONE) {    // Some type of resize 
    353                 // Set x/yDrag (unfortunately these need to be different for each edge) 
    354                 if (resizeType & RESIZE_TYPE.L) 
    355                     xDrag = w + cx; 
    356                 else if (resizeType & RESIZE_TYPE.R) 
    357                     xDrag = w - cx; 
    358                  
    359                 if (resizeType & RESIZE_TYPE.T) 
    360                     yDrag = h + cy; 
    361                 else if (resizeType & RESIZE_TYPE.B) 
    362                     yDrag = h - cy; 
    363                  
    364                 // Add the callbacks (they use resizeType which has already been set) 
    365                 gui_.addClickCallback (&endCallback); 
    366                 gui_.addMotionCallback (&resizeCallback); 
    367             } else {                             // window is being moved 
    368                 xDrag = cx - x; 
    369                 yDrag = cy - y; 
    370                  
    371                 gui_.addClickCallback (&endCallback); 
    372                 gui_.addMotionCallback (&moveCallback); 
    373             } 
    374         } 
    375     } 
    376      
    377     void draw () { 
    378         // background 
    379         rend.drawWindow (x,y, w,h); 
    380          
    381         // Tell the widget to draw itself: 
    382         widget.draw(); 
    383     } 
    384     //END IWidget methods 
    385      
    386 private: 
    387     alias IRenderer.BorderDimensions BorderDimensions; 
    388     alias IRenderer.RESIZE_TYPE RESIZE_TYPE; 
    389      
    390     //BEGIN Window moving and resizing 
    391     void moveCallback (wdabs cx, wdabs cy) { 
    392         setPosition (cx-xDrag, cy-yDrag); 
    393     } 
    394     void resizeCallback (wdabs cx, wdabs cy) { 
    395         debug scope(failure) 
    396                 logger.trace ("resizeCallback: failure"); 
    397          
    398         // This function is only called if some resize is going to happen. 
    399         // x,y are used as parameters to setPosition as well as being affected by it; somewhat 
    400         // pointless but fairly effective. 
    401          
    402         if (resizeType & RESIZE_TYPE.L) { 
    403             wdim xSize = xDrag - cx; 
    404             if (xSize < mw) xSize = mw; // clamp 
    405             x += w - xSize; 
    406             setWidth (xSize, 1); 
    407         } 
    408         else if (resizeType & RESIZE_TYPE.R) { 
    409             setWidth (xDrag + cx, -1); 
    410         } 
    411         if (resizeType & RESIZE_TYPE.T) { 
    412             wdim ySize = yDrag - cy; 
    413             if (ySize < mh) ySize = mh; 
    414             y += h - ySize; 
    415             setHeight (ySize, 1); 
    416         } 
    417         else if (resizeType & RESIZE_TYPE.B) { 
    418             setHeight (yDrag + cy, -1); 
    419         } 
    420          
    421         // Moves lower (x,y) coordinate if necessary and repositions any sub-widgets moved by the 
    422         // resizing: 
    423         setPosition (x, y); 
    424     } 
    425     bool endCallback (wdabs cx, wdabs cy, ubyte b, bool state) { 
    426         if (b == 1 && state == false) { 
    427             // The mouse shouldn't have moved since the motion callback 
    428             // was last called, so there's nothing else to do now. 
    429             gui_.removeCallbacks (cast(void*) this); 
    430              
    431             return true;    // we've handled the up-click 
    432         } 
    433     &