Changeset 90:b525ff28774b

Show
Ignore:
Timestamp:
10/01/08 18:37:51 (3 months ago)
Author:
Diggory Hardy <diggory.hardy@gmail.com>
branch:
default
Message:

Widgets generated dynamically from a list can now be standard widgets selected from data files.

Started on allowing alignment to be shared between instances of a layout widget in a dynamic list (to allow column alignment of list's rows).

Files:

Legend:

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

    r89 r90  
    44 
    55In progress: 
     6Layout alignment sharing for instances of same widgetID. 
    67 
    78 
     
    1415To do (importance 0-5: 0 pointless, 1 no obvious impact now, 2 todo sometime, 3 useful, 4 important, 5 urgent): 
    1516Also see todo.txt and FIXME/NOTE comment marks. 
     174   Data saving for widgetIDs with multiple instances? 
    16183   Try to correlate names of option sections more. (i.e. symbol name, class name, name of i18n translation file) 
    17193   Use of dtors - don't rely on them? Or what happens when init throws during creation - relying on undefined behaviour. 
  • codeDoc/todo.txt

    r75 r90  
    55GUI: 
    66->  Widgets: 
    7     ->  rethink how widgets are created and receive creation data, so that they don't have to be created by the Window 
    8     ->  scripted widgets 
    9     ->  decent rendering/theme system 
    10     ->  lists from content lists 
    11 ->  Content: 
    12     ->  lists 
     7->  rethink how widgets are created and receive creation data, so that they don't have to be created by the Window 
     8->  scripted widgets 
     9->  decent rendering/theme system 
     10->  lists from content lists 
    1311 
    1412 
    15 + shows current preference: 
     13Scratchpad area for ideas: 
     14 
    1615 
    1716Redesign: 
    18 ->  requirements 
    19     ->  widgets can receive int and string data types 
    2017->  possibilities 
    21     ->  per-widget merging (i.e. separate tag(s) for each widget's data)? 
    22         ->  if a widget with sub-widgets is defined in a base file, but redesigned in a derived file, any unused widgets with data resulting are not created 
    23         ->  if design changes in a base file, while the old design was modified in a derived file, will the result be sane? 
    24         ->  if a locally modified gui is updated upstream (so the base files change), should: 
    25             ->  the local modifications persist? 
    26             ->  the local modifications be lost? 
    27             ->  the widgets be merged? 
    28                 ->  potentially gives the best of both worlds 
    29                 ->  should original widgets used as sub-widgets of modified widgets: 
    30                     ->  use original data (from base file), so they can be updated? 
    31                     ->  have data copied, also impacting unmodified portions of window using same widget? 
    32                     ->  have data copied under a new ID? 
    33             ->  the user be asked? 
    34                 ->  requires copy of old data... 
    35     +>  widget data per-gui instead of per-window? 
    36         ->  identical sub-widget could be used in multiple windows more easily, when data file is hand edited 
    37         ->  in-gui editor will probably copy data under a new ID anyway 
    38         +>  Maybe change to: 
    39             ->  One section per version, containing widget data 
    40                 ->  can inherit from other sections 
    41             ->  Which section (version of gui design) to use saved in header 
    42             ->  Either/or: 
    43                 ->  Windows defined in header by primary widget 
    44                 +>  Windows become ordinary sub-widgets or some parent widget, gui becomes a parent widget 
    45     +>  widget data no longer has "construction" and "mutable" variations 
    46         +>  widgets know their IDs and update their data whenever necessary, via the gui, into a new data set, which is saved upon exit 
     18    ->  How widgets get their sub-widgets: 
     19        ->  (current) Ask manager to create widget from manager's data with ID from widget's data. 
     20            ->  seems to work well; allows widgets some control over creation of children 
     21            ->  now extended to support instancing & passing content; seems to fulfill requirements 
     22        ->  (alternate) Pass widget a list of pointers to all sub-widgets 
     23            ->  manager creates all static (from data files) widets 
     24            ->  other widgets can create dynamic widgets as or to pass to sub-widgets 
     25            ->  widget data needs a portion which is explicitly IDs for subwidgets, or data needs reforming into a tree 
  • data/conf/gui.mtt

    r80 r90  
    33<char[]|Design="Working"> 
    44{Working} 
    5 <WidgetData|root={0:[0x8100,3,3],1:["square","blank","square","blank","content","blank","square","blank","square"]}> 
     5<WidgetData|root={0:[0xC100,3,3],1:["square","blank","square","blank","content","blank","square","blank","square"]}> 
    66<WidgetData|square={0:[0x1,6,6]}> 
    7 <WidgetData|content={0:[0x8100,4,2],1:["floating","button","blank","blank","blank","opts","blank","blank"]}> 
     7<WidgetData|content={0:[0xC100,4,2],1:["floating","button","blank","blank","blank","opts","blank","blank"]}> 
    88<WidgetData|button={0:[0x10,200,200]}> 
    99<WidgetData|blank={0:[0x2]}> 
    10 <WidgetData|opts={0:[0x8110, 0xfe8c00]}> 
     10<WidgetData|opts={0:[0x8110],1:["optBox"]}> 
     11<WidgetData|optBox={0:[0xC100, 1,3],1:["optVal","optSep","optVal"]}> 
     12<WidgetData|optVal={0:[0x4020, 0xfe8c00]}> 
     13<WidgetData|optSep={0:[0x21, 0xff],1:["-=-"]}> 
    1114<WidgetData|floating={0:[0x8200,20,20],1:["text"]}> 
    1215<WidgetData|text={0:[0x21,0xFF0000],1:["Floating text"]}> 
  • mde/gui/WidgetManager.d

    r86 r90  
    191191import mde.gui.exception; 
    192192import mde.gui.widget.Ifaces; 
     193import mde.gui.content.Content; //NOTE - maybe move IContent to a separate module 
    193194import mde.gui.widget.createWidget; 
    194195 
     
    377378     
    378379     
    379     /** Create a widget by ID. */ 
    380     IChildWidget makeWidget (widgetID id, IParentWidget parent = null) { 
     380    /** Create a widget by ID. 
     381     * 
     382     * A widget instance is created from data found under ID. Multiple instances may be created. 
     383     * FIXME - data conflicts when saving? 
     384     * 
     385     * An IContent may be passed. This could contain many things, e.g. some basic data, a widget, 
     386     * multiple sub-IContents. It is only passed to the widget by createWidget if it's enumeration 
     387     * given in that module has the flag TAKES_CONTENT. */ 
     388    IChildWidget makeWidget (widgetID id, IContent content = null) { 
    381389        debug (mdeWidgets) logger.trace ("Creating widget \""~id~'"'); 
    382         return createWidget (this, curData[id], parent); 
     390        return createWidget (this, curData[id], content); 
    383391    } 
    384392     
  • mde/gui/content/Content.d

    r72 r90  
    2727 * Currently Content instances can only have their data set on creation. 
    2828 * Each Content class should provide a method to get it's content, e.g. the method text(). 
     29 * 
     30 * Extensions to content: 
     31 *  
     32 * These extensions require that a content can notify any dependants of changes. 
     33 *  
     34 * Use as a state switch (one option from an enumeration). E.g. a button/selection box could set a 
     35 * state, and a tabbed box could show a tab based on this. Or could represent an option. 
    2936 */ 
    3037// Don't include dimension/drawing stuff because it's renderer specific and content should not be! 
  • mde/gui/widget/Floating.d

    r82 r90  
    5454 * additionally an (x,y) coordinate for each subwidget (all x coords first, then all y coords). 
    5555 */ 
    56 class FloatingAreaWidget : SizableWidget, IParentWidget 
     56class FloatingAreaWidget : SizableWidget 
    5757{ 
    5858    this (IWidgetManager mgr, WidgetData data) { 
    5959        subWidgets.length = data.strings.length; 
    6060        foreach (i,s; data.strings) 
    61             subWidgets[i] = mgr.makeWidget (s, this); 
     61            subWidgets[i] = mgr.makeWidget (s); 
    6262        sWCoords.length = subWidgets.length; 
    6363         
  • mde/gui/widget/Ifaces.d

    r85 r90  
    2626public import mde.gui.types; 
    2727public import mde.gui.renderer.IRenderer; 
     28import mde.gui.content.Content; //NOTE - maybe move IContent to a separate module 
    2829 
    2930 
     
    4748 * This class handles widget rendering, input, loading and saving. 
    4849 *************************************************************************************************/ 
    49 interface IWidgetManager : IParentWidget 
     50interface IWidgetManager : IWidget 
    5051{ 
    5152    // Loading/saving: 
     
    5455     * Params: 
    5556     *  id      = Identifier, within data files, of the data for the widget. 
    56      *  parent  = Reference to the widget's parent, passed if the widget supports recieving it
     57     *  content = An IContent may be passed to some widgets on creation
    5758     * 
    5859     * Creates a widget, using the widget data with index id. Widget data is loaded from files, 
    5960     * and per design (multiple gui layouts, called designs, may exist; data is per design). 
    60      *  
    61      * Note: this method is only for widgets with an identifier; generic widgets instanciated in 
    62      * lists are created directly and have no WidgetData of their own. */ 
    63     IChildWidget makeWidget (widgetID id, IParentWidget parent = null); 
     61     */ 
     62    IChildWidget makeWidget (widgetID id, IContent content = null); 
    6463     
    6564    /** Record some changes, for saving. */ 
     
    107106 
    108107/************************************************************************************************* 
    109  * Interface for parent widgets, including the gui manager. 
    110  * 
    111  * A widget may call these methods on its parent, and on the gui manager. 
    112  *************************************************************************************************/ 
    113 interface IParentWidget : IWidget 
    114 { 
    115     // NOTE: Likely some day this interface will really be used. 
    116     // NOTE: What widget is NOT going to implement this? It will probably be inherited. 
    117 } 
    118  
    119  
    120 /************************************************************************************************* 
    121108 * Interface for (child) widgets, i.e. all widgets other than the manager. 
    122109 * 
    123110 * A widget is a region of a GUI window which handles rendering and user-interaction for itself 
    124  * and is able to communicate with its manager and parent/child widgets as necessary. 
     111 * and is able to communicate with its manager and child widgets as necessary. (Passing widgets 
     112 * a reference to their parent has not been found useful.) 
    125113 * 
    126114 * If a widget is to be creatable by IWidgetManager.makeWidget, it must be listed in the 
     
    136124 *  + where x is ... and y is ... +/ 
    137125 * this (IWidgetManager mgr, WidgetData data); 
     126 *  
     127 * /// The CTOR may take an IContent reference: 
     128 * this (IWidgetManager mgr, WidgetData data, IContent content); 
    138129 * ---------------------------------- 
    139130 * Where mgr is the widget manager and data is 
  • mde/gui/widget/TextWidget.d

    r77 r90  
    2727import mde.font.font; 
    2828 
    29 import tango.io.Stdout; 
    30  
    31 /// Adapter to ease use of ContentText 
    32 struct ContentAdapter(ContentT : IContent) { 
    33     void set (char[] cID, int col) { 
    34         if (font is null) font = FontStyle.get("default"); 
    35          
    36         static if (is(ContentT == ContentText)) { 
    37             content = getContentText (cID); 
    38         } else static if (is(ContentT == ContentInt)) { 
    39             content = getContentInt (cID); 
    40         } else static assert (false, "Unsupported content type"); 
    41         colour = Colour (cast(ubyte) (col >> 16u), 
    42                          cast(ubyte) (col >> 8u), 
    43                          cast(ubyte) col ); 
    44     } 
    45      
    46     void getDimensions (out wdsize w, out wdsize h) { 
    47         font.updateBlock (content.toString, textCache); 
    48         w = cast(wdim) textCache.w; 
    49         h = cast(wdim) textCache.h; 
    50     } 
    51      
    52     void draw (wdabs x, wdabs y) { 
    53         font.textBlock (x,y, content.toString, textCache, colour); 
    54     } 
    55      
    56     ContentT content; 
    57     TextBlock textCache; 
    58     Colour colour; 
    59     static FontStyle font; 
    60 } 
    61  
    6229/// Basic text widget 
    63 // FIXME (content, creation for different types) 
    64 class ContentWidget(ContentT : IContent) : Widget 
     30class TextLabelWidget : Widget 
    6531{ 
    6632    /** Constructor for a widget containing [fixed] content. 
     
    7238    this (IWidgetManager mgr, WidgetData data) { 
    7339        WDCheck (data, 2, 1); 
    74         text.set (data.strings[0], data.ints[1]); 
    75         text.getDimensions (mw, mh); 
     40        if (font is null) font = FontStyle.get("default"); 
     41        font.updateBlock (data.strings[0], textCache); 
     42        mw = cast(wdim) textCache.w; 
     43        mh = cast(wdim) textCache.h; 
     44        colour = Colour (data.ints[1]); 
    7645        super (mgr,data); 
    7746    } 
     
    7948    void draw () { 
    8049        super.draw(); 
    81         text.draw (x,y); 
     50        font.textBlock (x,y, text, textCache, colour); 
    8251    } 
    8352     
    8453protected: 
    85     ContentAdapter!(ContentT) text; 
     54    char[] text; 
     55    Colour colour; 
     56    TextBlock textCache; 
     57    static FontStyle font; 
    8658} 
    8759 
    88 alias ContentWidget!(ContentText) TextWidget; 
    89 alias ContentWidget!(ContentInt) IntWidget; 
    9060 
    91  
    92 /// Adapter to ease use of ContentOptionWidget 
    93 struct ContentOptionAdapter { 
     61/// Adapter to ease use of ContentLabelWidget 
     62struct ContentLabelAdapter { 
    9463    void set (IContent c, int col) { 
    9564        if (font is null) font = FontStyle.get("default"); 
     
    11786} 
    11887 
    119 /// Basic text widget 
    120 class ContentOptionWidget : Widget 
     88/// Basic widget displaying a label from a content. 
     89class ContentLabelWidget : Widget 
    12190{ 
    12291    this (IWidgetManager mgr, WidgetData data, IContent c) { 
    12392        WDCheck (data, 2, 0); 
    124         content.set (c, data.ints[1]); 
    125         content.getDimensions (mw, mh); 
     93        adapter.set (c, data.ints[1]); 
     94        adapter.getDimensions (mw, mh); 
    12695        super (mgr,data); 
    12796    } 
     
    12998    void draw () { 
    13099        super.draw(); 
    131         content.draw (x,y); 
     100        adapter.draw (x,y); 
    132101    } 
    133102     
    134103protected: 
    135     ContentOptionAdapter content
     104    ContentLabelAdapter adapter
    136105} 
  • mde/gui/widget/createWidget.d

    r85 r90  
    1414along with this program.  If not, see <http://www.gnu.org/licenses/>. */ 
    1515 
    16 /// GUI Widget module. 
     16/** This module contains code to create a widget based on an enumeration value passed at runtime. 
     17 * 
     18 * It could be a part of the WidgetLoader.makeWidget function, but having it here makes things 
     19 * tidier. */ 
    1720module mde.gui.widget.createWidget; 
    1821 
    1922import mde.gui.widget.Ifaces; 
    2023import mde.gui.exception : WidgetDataException; 
     24import mde.gui.content.Content; //NOTE - maybe move IContent to a separate module 
    2125 
    2226// Widgets to create: 
     
    3943 * --- 
    4044 * this (IWidgetManager mgr, WidgetData data); 
    41  * // Called if (data.ints[0] & WIDGET_TYPES.TAKES_PARENT): 
    42  * this (IWidgetManager mgr, WidgetData data, IParentWidget parent); 
     45 * // Called if (data.ints[0] & WIDGET_TYPES.TAKES_CONTENT): 
     46 * this (IWidgetManager mgr, WidgetData data, IContent content); 
    4347 * --- 
    4448 *************************************************************************************************/ 
    45 IChildWidget createWidget (IWidgetManager mgr, WidgetData data, IParentWidget parent) 
     49IChildWidget createWidget (IWidgetManager mgr, WidgetData data, IContent content) 
    4650in { 
    4751    assert (mgr !is null, "createWidget: mgr is null"); 
     
    7478/// Widget types. 
    7579enum WIDGET_TYPE : int { 
    76     TAKES_PARENT            = 0x4000,   // CTOR takes reference to its parent 
    77     PARENT                  = 0x8000,   // widget can have children 
     80    TAKES_CONTENT           = 0x4000,   // Flag indicates widget's this should be passed an IContent reference. 
     81    PARENT                  = 0x8000,   // widget can have children; not used by code (except in data files) 
    7882     
    7983    // Use widget names rather than usual capitals convention 
     
    8892    Button                  = 0x10, 
    8993     
    90     // content: 0x20 
    91     Text           = 0x21
    92     Int                = 0x22
     94    // labels: 0x20 
     95    ContentLabel       = TAKES_CONTENT | 0x20
     96    TextLabel               = 0x21
    9397     
    94     GridLayout              = PARENT | 0x100, 
     98    GridLayout              = TAKES_CONTENT | PARENT | 0x100, 
    9599    TrialContentLayout      = PARENT | 0x110, 
    96100     
     
    106110        "Debug", 
    107111        "Button", 
    108         "Text", 
    109         "Int", 
    110         "GridLayout", 
     112        "TextLabel", 
     113        "ContentLabel", 
    111114        "TrialContentLayout", 
    112         "FloatingArea"]; 
     115        "FloatingArea", 
     116        "GridLayout"]; 
    113117 
    114118/* Generates a binary search algorithm. */ 
     
    125129            ret ~=  "if (" ~ var ~ " == WIDGET_TYPE." ~ c ~ ") {\n" ~ 
    126130                    "   debug (mdeWidgets) logger.trace (\"Creating new "~c~"Widget.\");\n" ~ 
    127                     "   static if (WIDGET_TYPE."~c~" & WIDGET_TYPE.TAKES_PARENT)\n" ~ 
    128                     "       return new " ~ c ~ "Widget (mgr, data, parent);\n" ~ 
     131                    "   static if (WIDGET_TYPE."~c~" & WIDGET_TYPE.TAKES_CONTENT)\n" ~ 
     132                    "       return new " ~ c ~ "Widget (mgr, data, content);\n" ~ 
    129133                    "   else\n" ~ 
    130134                    "       return new " ~ c ~ "Widget (mgr, data);\n" ~ 
     
    135139    } 
    136140} 
     141 
     142debug { // check items in WIDGETS are listed in order 
     143    char[] WIDGETS_check () { 
     144        char[] ret; 
     145        for (int i = WIDGETS.length-2; i > 0; --i) { 
     146            ret ~= "WIDGET_TYPE."~WIDGETS[i] ~" >= WIDGET_TYPE."~ WIDGETS[i+1]; 
     147            if (i>1) ret ~= " || "; 
     148        } 
     149        return ret; 
     150    } 
     151    mixin ("static if ("~WIDGETS_check~") 
     152        static assert (false, \"WIDGETS is not in order!\");"); 
     153} 
  • mde/gui/widget/layout.d

    r85 r90  
    2222import mde.gui.widget.TextWidget; 
    2323import mde.gui.content.options; 
     24import mde.gui.content.Content; 
    2425 
    2526debug { 
     
    4849     * [widgetID, r, c, w11, w12, ..., w1c, ..., wr1, ..., wrc] 
    4950     * where r and c are the number of rows and columns, and wij is the ID (from parent Window's 
    50      * list) for the widget in row i and column j. The number of parameters must be r*c + 3. */ 
    51     this (IWidgetManager mgr, WidgetData data) { 
     51     * list) for the widget in row i and column j. The number of parameters must be r*c + 3. 
     52     *  
     53     * The content parameter is passed on to all children accepting an IContent. */ 
     54    this (IWidgetManager mgr, WidgetData data, IContent content) { 
    5255        // Get grid size and check data 
    5356        // Check sufficient data for rows, cols, and possibly row/col widths. 
     
    6770        subWidgets.length = rows*cols; 
    6871        foreach (i, ref subWidget; subWidgets) { 
    69             subWidget = mgr.makeWidget (data.strings[i]); 
     72            subWidget = mgr.makeWidget (data.strings[i], content); 
    7073        } 
    7174         
     
    7376         
    7477        if (data.ints.length == 3 + rows + cols) { 
    75             col.setCheck (cast(wdim[]) data.ints[3..cols+3]); 
    76             row.setCheck (cast(wdim[]) data.ints[cols+3..$]); 
     78            col.setWidths (cast(wdim[]) data.ints[3..cols+3]); 
     79            row.setWidths (cast(wdim[]) data.ints[cols+3..$]); 
    7780        } else { 
    78             col.dupMin
    79             row.dupMin
     81            col.setWidths
     82            row.setWidths
    8083        } 
    8184        adjustCache; 
     
    106109        debug scope (failure) 
    107110                logger.warn ("TrialContentLayoutWidget: failure"); 
    108         WDCheck (data, 2); 
     111        WDCheck (data, 1, 1); 
    109112         
    110113        OptionList optsList = OptionList.trial(); 
     
    114117        // Get all sub-widgets 
    115118        subWidgets.length = rows*cols; 
    116         WidgetData COWData; 
    117         COWData.ints = [0, data.ints[1]]; 
    118119        foreach (i, c; optsList.list) { 
    119             subWidgets[i] = new ContentOptionWidget (mgr, COWData, c); 
     120            subWidgets[i] = mgr.makeWidget (data.strings[0], c); 
    120121        } 
    121122        super (mgr, data); 
    122123         
    123124        // Set col/row widths to minimals. 
    124         col.dupMin
    125         row.dupMin
     125        col.setWidths
     126        row.setWidths
    126127        adjustCache; 
    127128    } 
     
    151152     * the call to genCachedConstructionData can be moved to the derived this() methods.) 
    152153     *  
    153      * Derived constructors should call either dupMin or setCheck on col and row, and then call 
     154     * Derived constructors should call setWidths on col and row, and then call 
    154155     * adjustCache, after calling this. */ 
    155156    protected this (IWidgetManager mgr, WidgetData data) { 
    156157        super (mgr, data); 
    157158         
    158         // Needn't be set before genCachedConstructionData is called: 
    159         col.setColWidth = &setColWidth
    160         row.setColWidth = &setRowHeight
     159        // Create cell aligners with appropriate col/row adjustment function 
     160        col = (new AlignColumns (cols)).addSetCallback (&setColWidth)
     161        row = (new AlignColumns (rows)).addSetCallback (&setRowHeight)
    161162         
    162163        // Calculate cached construction data 
     
    166167    /** Generates cached mutable data. 
    167168     * 
    168      * Should be called by adjust() after setting col and row widths (currently via dupMin or 
    169      * setCheck). */ 
     169     * Should be called by adjust() after calling setWidths. */ 
    170170    void adjustCache () { 
    171171        // Generate cached mutable data 
     
    375375    IChildWidget[] subWidgets; 
    376376     
    377     /* Widths, positions, etc., either of columns or of rows 
    378      * 
    379      * The purpose of this struct is mostly to unify functionality which must work the same on both 
    380      * horizontal and vertical cell placement. 
    381      * 
    382      * Most notation corresponds to horizontal layout (columns), simply for easy of naming. */ 
    383     struct CellDimensions { 
    384         wdim[] pos, // relative position (cumulative width[i-1] plus spacing) 
    385               width,    // current widths 
    386               minWidth; // minimal widths (set by genCachedConstructionData) 
    387         bool[] sizable; // true if col is resizable (set by genCachedConstructionData) 
    388         myDiff firstSizable,    // first col which is resizable, negative if none 
    389                lastSizable; // as above, but last (set by genCachedConstructionData) 
    390         myDiff resizeD, // resize down from this index (<0 if not resizing) 
    391                resizeU; // and up from this index 
    392         wdim spacing;   // used by genPositions (which cannot access the layout class's data) 
    393         /* This is a delegate to a enclosing class's function, since: 
    394          * a different implementation is needed for cols or rows 
    395          * we're unable to access enclosing class members directly */ 
    396         void delegate (myIt,wdim,int) setColWidth;  // set width of a column, with resize direction 
    397          
    398         void dupMin () { 
    399             width = minWidth.dup; 
    400         } 
    401         void setCheck (wdim[] data) { 
    402             // Set to provided data: 
    403             width = data; 
    404             // And check sizes are valid: 
    405             foreach (i, m; minWidth) 
    406                 // if fixed width or width is less than minimum: 
    407                 if (!sizable[i] || width[i] < m) 
    408                     width[i] = m; 
    409         } 
    410          
    411         // Generate position infomation and return total width (i.e. widget width/height) 
    412         wdim genPositions () { 
    413             pos.length = minWidth.length; 
     377    AlignColumns col, row; 
     378
     379 
     380/// Position information on top of widths. 
     381//FIXME - merge classes back together? 
     382class AlignColumns : AlignWidths 
     383
     384    /// See AlignWidths.this 
     385    this (myIt columns) { 
     386        super (columns); 
     387    } 
     388     
     389    /** Add a callback to be called to notify changes in a column's width. 
     390    *  
     391    * All callbacks added are called on a width change so that multiple objects may share a 
     392    * CellAlign object. */ 
     393    typeof(this) addSetCallback (void delegate (myIt,wdim,int) setCW) { 
     394        assert (setCW, "CellAlign.this: setCW is null (code error)"); 
     395        setWidthCb ~= setCW; 
     396        return this; 
     397    } 
     398     
     399    /** Generate position infomation and return total width of columns. */ 
     400    wdim genPositions () { 
     401        pos.length = minWidth.length; 
     402         
     403        wdim x = 0; 
     404        foreach (i, w; width) { 
     405            pos[i] = x; 
     406            x += w + spacing; 
     407        } 
     408        return x - spacing; 
     409    } 
     410     
     411    // Get the row/column a click occured in 
     412    // Returns -i if in space to left of col i, or i if on col i 
     413    myDiff getCell (wdim l) { 
     414        myDiff i = minWidth.length - 1;     // starting from right... 
     415        while (l < pos[i]) {                // decrement while left of this column 
     416            debug assert (i > 0, "getCell: l < pos[0] (code error)"); 
     417            --i; 
     418        }                                   // now (l >= pos[i]) 
     419        if (l >= pos[i] + width[i]) {       // between columns 
     420            debug assert (i+1 < minWidth.length, "getCell: l >= pos[$-1] + width[$-1] (code error)"); 
     421            return -i - 1;                  // note: i might be 0 so cannot just return -i 
     422        } 
     423        return i; 
     424    } 
     425     
     426    // Calculate resizeU/resizeD, and return true if unable to resize. 
     427    bool findResize (wdim l) { 
     428        resizeU = -getCell (l);             // potential start for upward-resizes 
     429        if (resizeU <= 0) return true;      // not on a space between cells 
     430            resizeD = resizeU - 1;              // potential start for downward-resizes 
    414431             
    415             wdim x = 0; 
    416             foreach (i, w; width) { 
    417                 pos[i] = x; 
    418                 x += w + spacing; 
     432        while (!sizable[resizeU]) {         // find first actually resizable column (upwards) 
     433            ++resizeU; 
     434            if (resizeU >= minWidth.length) {       // cannot resize 
     435                resizeU = -1; 
     436                return true; 
    419437            } 
    420             return x - spacing; 
    421         } 
    422          
    423         // Get the row/column a click occured in 
    424         // Returns -i if in space to left of col i, or i if on col i 
    425         myDiff getCell (wdim l) { 
    426             myDiff i = minWidth.length - 1; // starting from right... 
    427             while (l < pos[i]) {        // decrement while left of this column 
    428                 debug assert (i > 0, "getCell: l < pos[0] (code error)"); 
    429                 --i; 
    430             }                   // now (l >= pos[i]) 
    431             if (l >= pos[i] + width[i]) {   // between columns 
    432                 debug assert (i+1 < minWidth.length, "getCell: l >= pos[$-1] + width[$-1] (code error)"); 
    433                 return -i - 1;          // note: i might be 0 so cannot just return -i 
     438        } 
     439             
     440        while (!sizable[resizeD]) {         // find first actually resizable column (downwards) 
     441            --resizeD; 
     442            if (resizeD < 0) {              // cannot resize 
     443                resizeU = -1;       // resizeU is tested to check whether resizes are possible 
     444                return true; 
    434445            } 
    435             return i; 
    436         } 
    437          
    438         // Calculate resizeU/resizeD, and return true if unable to resize. 
    439         bool findResize (wdim l) { 
    440             resizeU = -getCell (l);     // potential start for upward-resizes 
    441             if (resizeU <= 0) return true;  // not on a space between cells 
    442             resizeD = resizeU - 1;      // potential start for downward-resizes 
    443              
    444             while (!sizable[resizeU]) {     // find first actually resizable column (upwards) 
    445                 ++resizeU; 
    446                 if (resizeU >= minWidth.length) {   // cannot resize 
    447                     resizeU = -1; 
    448                     return true; 
     446        } 
     447         
     448        return false;                       // can resize 
     449    } 
     450     
     451    /* Adjust the total size of rows/columns (including spacing) by diff. 
     452    * 
     453    * Params: 
     454    *  diff = amount to increase/decrease the total size 
     455    *  start= index for col/row to start resizing on 
     456    *  incr = direction to resize in (added to index each step). Must be either -1 or +1. 
     457    * 
     458    * Returns: 
     459    *  The amount adjusted. This may be larger than diff, since cellD is clamped by cellDMin. 
     460    * 
     461    * Note: Check variable used for start is valid before calling! If a non-sizable column's 
     462    *  index is passed, this should get increased (if diff > 0) but not decreased. 
     463    */ 
     464    wdim adjustCellSizes (wdim diff, myDiff start, int incr) 
     465    in { 
     466        assert (width, "CellAlign.adjustCellSizes: width is null (code error)"); 
     467        // Most likely if passed negative when sizing is disabled: 
     468        assert (start >= 0 && start < minWidth.length, "adjustCellSizes: invalid start"); 
     469        debug assert (incr == 1 || incr == -1, "adjustCellSizes: invalid incr"); 
     470    } body { 
     471        debug scope(failure) 
     472        logger.trace ("adjustCellSizes: failure"); 
     473        myDiff i = start; 
     474        if (diff > 0) {             // increase size of first resizable cell 
     475            width[i] += diff; 
     476            foreach (dg; setWidthCb) 
     477                dg(i, width[i], incr); 
     478        } 
     479        else if (diff < 0) {        // decrease 
     480            wdim rd = diff;         // running diff 
     481            aCSwhile: 
     482            while (true) { 
     483                width[i] += rd;     // decrease this cell's size (but may be too much) 
     484                rd = width[i] - minWidth[i]; 
     485                if (rd >= 0) {      // OK; we're done 
     486                    foreach (dg; setWidthCb) 
     487                        dg(i, width[i], incr); 
     488                    break;          // we hit the mark exactly: diff is correct 
     489                } 
     490                 
     491                // else we decreased it too much! 
     492                width[i] = minWidth[i]; 
     493                foreach (dg; setWidthCb) 
     494                    dg(i, width[i], incr); 
     495                // rd is remainder to decrease by 
     496                 
     497                 
     498                bool it = true;     // iterate (force first time) 
     499                while (it) { 
     500                    i += incr; 
     501                    if (i < 0 || i >= minWidth.length) {    // run out of next cells 
     502                        diff -= rd; // still had rd left to decrease 
     503                        break aCSwhile;     // exception: Array index out of bounds 
     504                    } 
     505                    it = !sizable[i];       // iterate again if row/col isn't resizable 
    449506                } 
    450507            } 
    451              
    452             while (!sizable[resizeD]) {     // find first actually resizable column (downwards) 
    453                 --resizeD; 
    454                 if (resizeD < 0) {      // cannot resize 
    455                     resizeU = -1;   // resizeU is tested to check whether resizes are possible 
    456                     return true; 
    457                 } 
    458             } 
    459              
    460             return false;           // can resize 
    461         } 
    462          
    463         /* Adjust the total size of rows/columns (including spacing) by diff. 
    464         * 
    465         * Params: 
    466         *  diff = amount to increase/decrease the total size 
    467         *  start= index for col/row to start resizing on 
    468         *  incr = direction to resize in (added to index each step). Must be either -1 or +1. 
    469         * 
    470         * Returns: 
    471         *  The amount adjusted. This may be larger than diff, since cellD is clamped by cellDMin. 
    472         * 
    473         * Note: Check variable used for start is valid before calling! If a non-sizable column's 
    474         *  index is passed, this should get increased (if diff > 0) but not decreased. 
    475         */ 
    476         wdim adjustCellSizes (wdim diff, myDiff start, int incr) 
    477         in { 
    478             // Could occur if constructor doesn't call dupMin/setCheck (code error): 
    479             assert (width !is null, "adjustCellSizes: width is null"); 
    480             // Most likely if passed negative when sizing is disabled: 
    481             assert (start >= 0 && start < minWidth.length, "adjustCellSizes: invalid start"); 
    482             assert (incr == 1 || incr == -1, "adjustCellSizes: invalid incr"); 
    483             assert (setColWidth !is null, "adjustCellSizes: setColWidth is null"); 
    484         } body { 
    485             debug scope(failure) 
    486                     logger.trace ("adjustCellSizes: failure"); 
    487             myDiff i = start; 
    488             if (diff > 0) {     // increase size of first resizable cell 
    489                 width[i] += diff; 
    490                 setColWidth (i, width[i], incr); 
    491             } 
    492             else if (diff < 0) {    // decrease 
    493                 wdim rd = diff;     // running diff 
    494                 aCSwhile: 
    495                 while (true) { 
    496                     width[i] += rd; // decrease this cell's size (but may be too much) 
    497                     rd = width[i] - minWidth[i]; 
    498                     if (rd >= 0) {  // OK; we're done 
    499                         setColWidth (i, width[i], incr);    // set new width 
    500                         break;      // we hit the mark exactly: diff is correct 
    501                     } 
    502                      
    503                     // else we decreased it too much! 
    504                     width[i] = minWidth[i]; 
    505                     setColWidth (i, width[i], incr); 
    506                     // rd is remainder to decrease by 
    507                      
    508                      
    509                     bool it = true; // iterate (force first time) 
    510                     while (it) { 
    511                         i += incr; 
    512                         if (i < 0 || i >= minWidth.length) {    // run out of next cells 
    513                             diff -= rd; // still had rd left to decrease 
    514                             break aCSwhile; // exception: Array index out of bounds 
    515                         } 
    516                         it = !sizable[i];   // iterate again if row/col isn't resizable 
    517                     } 
    518                 } 
    519             } 
    520             // else no adjustment needed (diff == 0) 
    521              
    522             genPositions; 
    523             return diff; 
    524         } 
    525          
    526         void resize (wdim diff) { 
    527             if (resizeU <= 0) return; 
    528              
    529             // do shrinking first (in case we hit the minimum) 
    530             if (diff >= 0) { 
    531                 diff = -adjustCellSizes (-diff, resizeU, 1); 
    532                 adjustCellSizes (diff, resizeD, -1); 
    533             } else { 
    534                 diff = -adjustCellSizes (diff, resizeD, -1); 
    535                 adjustCellSizes (diff, resizeU, 1); 
    536             } 
    537         } 
    538     } 
    539     CellDimensions col, row; 
    540      
    541     // Index types. Note that in some cases they need to hold negative values. 
    542     // int is used for resizing direction (although ptrdiff_t would be more appropriate), 
    543     // since the value must always be -1 or +1 and int is smaller on X86_64. 
    544     alias size_t myIt; 
    545     alias ptrdiff_t myDiff; 
     508        } 
     509        // else no adjustment needed (diff == 0) 
     510         
     511        genPositions; 
     512        return diff; 
     513    } 
     514     
     515    void resize (wdim diff) 
     516    { 
     517        if (resizeU <= 0) return; 
     518         
     519        // do shrinking first (in case we hit the minimum) 
     520        if (diff >= 0) { 
     521            diff = -adjustCellSizes (-diff, resizeU, 1); 
     522            adjustCellSizes (diff, resizeD, -1); 
     523        } else { 
     524            diff = -adjustCellSizes (diff, resizeD, -1); 
     525            adjustCellSizes (diff, resizeU, 1); 
     526        } 
     527    } 
     528     
     529private: 
     530    wdim[]  pos;                // relative position (cumulative width[i-1] plus spacing) 
     531    wdim    spacing;            // used by genPositions (which cannot access the layout class's data) 
     532    // Callbacks used to actually adjust a column's width: 
     533    void delegate (myIt,wdim,int) setWidthCb[]; // set width of a column, with resize direction 
    546534} 
     535 
     536/************************************************************************************************** 
     537 * Alignment device 
     538 *  
     539 * E.g. can control widths of columns within a grid, and provide sensible resizing, respecting the 
     540 * minimal width required by each cell in a column. Is not restricted to horizontal widths, but to 
     541 * ease descriptions, a horizontal context (column widths) is assumed. 
     542 *  
     543 * Cells should be of type IChildWidget. 
     544 *  
     545 * FIXME: 
     546 * Cells are not directly interacted with, but minimal widths for each column are passed, and 
     547 * callback functions are used to adjust the width of any column. 
     548 *************************************************************************************************/ 
     549//FIXME: how to set cell positions after resizes? 
     550/+ FIXME - remove position information from here, removing need of horizontal alignment of cells 
     551 into columns. On resizing, don't permanently adjust sizes until callback ends (mouse button 
     552 released). Until then, always readjust from current "permanent" size. +/ 
     553class AlignWidths 
     554{ 
     555    /** Create an instance. After creation, the number of columns can only be changed by calling 
     556     * reset. 
     557     *  
     558     * After creation, minimal widths should be set for all columns (minWidth) and 
     559     * setWidths must be called before other functions are used. */ 
     560    this (myIt columns) { 
     561        reset (columns); 
     562    } 
     563     
     564    /** Reset all column information (only keep set callbacks). 
     565     * 
     566     * Widths should be set after calling, as on creation. */ 
     567    void reset (myIt columns) { 
     568        if (columns < 1) 
     569            throw new GuiException("AlignWidths: created with <1 column"); 
     570        minWidth = new wdim[columns]; 
     571        width = null;   // enforce calling setWidths after this 
     572    } 
     573     
     574    /** Initialize widths as minimal widths. */ 
     575    void setWidths () { 
     576        width = minWidth.dup; 
     577    } 
     578    /** Initialize widths from supplied list, checking validity. */ 
     579    void setWidths (wdim[] data) { 
     580        // Set to provided data: 
     581        width = data; 
     582        // And check sizes are valid: 
     583        foreach (i, m; minWidth) 
     584            // if fixed width or width is less than minimum: 
     585