Changeset 90:b525ff28774b
- Timestamp:
- 10/01/08 18:37:51 (3 months ago)
- Files:
-
- codeDoc/jobs.txt (modified) (2 diffs)
- codeDoc/todo.txt (modified) (1 diff)
- data/conf/gui.mtt (modified) (1 diff)
- mde/gui/WidgetManager.d (modified) (2 diffs)
- mde/gui/content/Content.d (modified) (1 diff)
- mde/gui/widget/Floating.d (modified) (1 diff)
- mde/gui/widget/Ifaces.d (modified) (5 diffs)
- mde/gui/widget/TextWidget.d (modified) (5 diffs)
- mde/gui/widget/createWidget.d (modified) (7 diffs)
- mde/gui/widget/layout.d (modified) (9 diffs)
- mde/types/Colour.d (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
codeDoc/jobs.txt
r89 r90 4 4 5 5 In progress: 6 Layout alignment sharing for instances of same widgetID. 6 7 7 8 … … 14 15 To do (importance 0-5: 0 pointless, 1 no obvious impact now, 2 todo sometime, 3 useful, 4 important, 5 urgent): 15 16 Also see todo.txt and FIXME/NOTE comment marks. 17 4 Data saving for widgetIDs with multiple instances? 16 18 3 Try to correlate names of option sections more. (i.e. symbol name, class name, name of i18n translation file) 17 19 3 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 5 5 GUI: 6 6 -> 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 13 11 14 12 15 + shows current preference: 13 Scratchpad area for ideas: 14 16 15 17 16 Redesign: 18 -> requirements19 -> widgets can receive int and string data types20 17 -> 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 3 3 <char[]|Design="Working"> 4 4 {Working} 5 <WidgetData|root={0:[0x 8100,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"]}> 6 6 <WidgetData|square={0:[0x1,6,6]}> 7 <WidgetData|content={0:[0x 8100,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"]}> 8 8 <WidgetData|button={0:[0x10,200,200]}> 9 9 <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:["-=-"]}> 11 14 <WidgetData|floating={0:[0x8200,20,20],1:["text"]}> 12 15 <WidgetData|text={0:[0x21,0xFF0000],1:["Floating text"]}> mde/gui/WidgetManager.d
r86 r90 191 191 import mde.gui.exception; 192 192 import mde.gui.widget.Ifaces; 193 import mde.gui.content.Content; //NOTE - maybe move IContent to a separate module 193 194 import mde.gui.widget.createWidget; 194 195 … … 377 378 378 379 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) { 381 389 debug (mdeWidgets) logger.trace ("Creating widget \""~id~'"'); 382 return createWidget (this, curData[id], parent);390 return createWidget (this, curData[id], content); 383 391 } 384 392 mde/gui/content/Content.d
r72 r90 27 27 * Currently Content instances can only have their data set on creation. 28 28 * 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. 29 36 */ 30 37 // Don't include dimension/drawing stuff because it's renderer specific and content should not be! mde/gui/widget/Floating.d
r82 r90 54 54 * additionally an (x,y) coordinate for each subwidget (all x coords first, then all y coords). 55 55 */ 56 class FloatingAreaWidget : SizableWidget , IParentWidget56 class FloatingAreaWidget : SizableWidget 57 57 { 58 58 this (IWidgetManager mgr, WidgetData data) { 59 59 subWidgets.length = data.strings.length; 60 60 foreach (i,s; data.strings) 61 subWidgets[i] = mgr.makeWidget (s , this);61 subWidgets[i] = mgr.makeWidget (s); 62 62 sWCoords.length = subWidgets.length; 63 63 mde/gui/widget/Ifaces.d
r85 r90 26 26 public import mde.gui.types; 27 27 public import mde.gui.renderer.IRenderer; 28 import mde.gui.content.Content; //NOTE - maybe move IContent to a separate module 28 29 29 30 … … 47 48 * This class handles widget rendering, input, loading and saving. 48 49 *************************************************************************************************/ 49 interface IWidgetManager : I ParentWidget50 interface IWidgetManager : IWidget 50 51 { 51 52 // Loading/saving: … … 54 55 * Params: 55 56 * 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. 57 58 * 58 59 * Creates a widget, using the widget data with index id. Widget data is loaded from files, 59 60 * 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); 64 63 65 64 /** Record some changes, for saving. */ … … 107 106 108 107 /************************************************************************************************* 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 : IWidget114 {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 /*************************************************************************************************121 108 * Interface for (child) widgets, i.e. all widgets other than the manager. 122 109 * 123 110 * 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.) 125 113 * 126 114 * If a widget is to be creatable by IWidgetManager.makeWidget, it must be listed in the … … 136 124 * + where x is ... and y is ... +/ 137 125 * this (IWidgetManager mgr, WidgetData data); 126 * 127 * /// The CTOR may take an IContent reference: 128 * this (IWidgetManager mgr, WidgetData data, IContent content); 138 129 * ---------------------------------- 139 130 * Where mgr is the widget manager and data is mde/gui/widget/TextWidget.d
r77 r90 27 27 import mde.font.font; 28 28 29 import tango.io.Stdout;30 31 /// Adapter to ease use of ContentText32 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 62 29 /// Basic text widget 63 // FIXME (content, creation for different types) 64 class ContentWidget(ContentT : IContent) : Widget 30 class TextLabelWidget : Widget 65 31 { 66 32 /** Constructor for a widget containing [fixed] content. … … 72 38 this (IWidgetManager mgr, WidgetData data) { 73 39 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]); 76 45 super (mgr,data); 77 46 } … … 79 48 void draw () { 80 49 super.draw(); 81 text.draw (x,y);50 font.textBlock (x,y, text, textCache, colour); 82 51 } 83 52 84 53 protected: 85 ContentAdapter!(ContentT) text; 54 char[] text; 55 Colour colour; 56 TextBlock textCache; 57 static FontStyle font; 86 58 } 87 59 88 alias ContentWidget!(ContentText) TextWidget;89 alias ContentWidget!(ContentInt) IntWidget;90 60 91 92 /// Adapter to ease use of ContentOptionWidget 93 struct ContentOptionAdapter { 61 /// Adapter to ease use of ContentLabelWidget 62 struct ContentLabelAdapter { 94 63 void set (IContent c, int col) { 95 64 if (font is null) font = FontStyle.get("default"); … … 117 86 } 118 87 119 /// Basic text widget120 class Content OptionWidget : Widget88 /// Basic widget displaying a label from a content. 89 class ContentLabelWidget : Widget 121 90 { 122 91 this (IWidgetManager mgr, WidgetData data, IContent c) { 123 92 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); 126 95 super (mgr,data); 127 96 } … … 129 98 void draw () { 130 99 super.draw(); 131 content.draw (x,y);100 adapter.draw (x,y); 132 101 } 133 102 134 103 protected: 135 Content OptionAdapter content;104 ContentLabelAdapter adapter; 136 105 } mde/gui/widget/createWidget.d
r85 r90 14 14 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 15 15 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. */ 17 20 module mde.gui.widget.createWidget; 18 21 19 22 import mde.gui.widget.Ifaces; 20 23 import mde.gui.exception : WidgetDataException; 24 import mde.gui.content.Content; //NOTE - maybe move IContent to a separate module 21 25 22 26 // Widgets to create: … … 39 43 * --- 40 44 * this (IWidgetManager mgr, WidgetData data); 41 * // Called if (data.ints[0] & WIDGET_TYPES.TAKES_ PARENT):42 * this (IWidgetManager mgr, WidgetData data, I ParentWidget parent);45 * // Called if (data.ints[0] & WIDGET_TYPES.TAKES_CONTENT): 46 * this (IWidgetManager mgr, WidgetData data, IContent content); 43 47 * --- 44 48 *************************************************************************************************/ 45 IChildWidget createWidget (IWidgetManager mgr, WidgetData data, I ParentWidget parent)49 IChildWidget createWidget (IWidgetManager mgr, WidgetData data, IContent content) 46 50 in { 47 51 assert (mgr !is null, "createWidget: mgr is null"); … … 74 78 /// Widget types. 75 79 enum WIDGET_TYPE : int { 76 TAKES_ PARENT = 0x4000, // CTOR takes reference to its parent77 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) 78 82 79 83 // Use widget names rather than usual capitals convention … … 88 92 Button = 0x10, 89 93 90 // content: 0x2091 Text = 0x21,92 Int = 0x22,94 // labels: 0x20 95 ContentLabel = TAKES_CONTENT | 0x20, 96 TextLabel = 0x21, 93 97 94 GridLayout = PARENT | 0x100,98 GridLayout = TAKES_CONTENT | PARENT | 0x100, 95 99 TrialContentLayout = PARENT | 0x110, 96 100 … … 106 110 "Debug", 107 111 "Button", 108 "Text", 109 "Int", 110 "GridLayout", 112 "TextLabel", 113 "ContentLabel", 111 114 "TrialContentLayout", 112 "FloatingArea"]; 115 "FloatingArea", 116 "GridLayout"]; 113 117 114 118 /* Generates a binary search algorithm. */ … … 125 129 ret ~= "if (" ~ var ~ " == WIDGET_TYPE." ~ c ~ ") {\n" ~ 126 130 " 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" ~ 129 133 " else\n" ~ 130 134 " return new " ~ c ~ "Widget (mgr, data);\n" ~ … … 135 139 } 136 140 } 141 142 debug { // 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 22 22 import mde.gui.widget.TextWidget; 23 23 import mde.gui.content.options; 24 import mde.gui.content.Content; 24 25 25 26 debug { … … 48 49 * [widgetID, r, c, w11, w12, ..., w1c, ..., wr1, ..., wrc] 49 50 * 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) { 52 55 // Get grid size and check data 53 56 // Check sufficient data for rows, cols, and possibly row/col widths. … … 67 70 subWidgets.length = rows*cols; 68 71 foreach (i, ref subWidget; subWidgets) { 69 subWidget = mgr.makeWidget (data.strings[i] );72 subWidget = mgr.makeWidget (data.strings[i], content); 70 73 } 71 74 … … 73 76 74 77 if (data.ints.length == 3 + rows + cols) { 75 col.set Check(cast(wdim[]) data.ints[3..cols+3]);76 row.set Check(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..$]); 77 80 } else { 78 col. dupMin;79 row. dupMin;81 col.setWidths; 82 row.setWidths; 80 83 } 81 84 adjustCache; … … 106 109 debug scope (failure) 107 110 logger.warn ("TrialContentLayoutWidget: failure"); 108 WDCheck (data, 2);111 WDCheck (data, 1, 1); 109 112 110 113 OptionList optsList = OptionList.trial(); … … 114 117 // Get all sub-widgets 115 118 subWidgets.length = rows*cols; 116 WidgetData COWData;117 COWData.ints = [0, data.ints[1]];118 119 foreach (i, c; optsList.list) { 119 subWidgets[i] = new ContentOptionWidget (mgr, COWData, c);120 subWidgets[i] = mgr.makeWidget (data.strings[0], c); 120 121 } 121 122 super (mgr, data); 122 123 123 124 // Set col/row widths to minimals. 124 col. dupMin;125 row. dupMin;125 col.setWidths; 126 row.setWidths; 126 127 adjustCache; 127 128 } … … 151 152 * the call to genCachedConstructionData can be moved to the derived this() methods.) 152 153 * 153 * Derived constructors should call either dupMin or setCheckon col and row, and then call154 * Derived constructors should call setWidths on col and row, and then call 154 155 * adjustCache, after calling this. */ 155 156 protected this (IWidgetManager mgr, WidgetData data) { 156 157 super (mgr, data); 157 158 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); 161 162 162 163 // Calculate cached construction data … … 166 167 /** Generates cached mutable data. 167 168 * 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. */ 170 170 void adjustCache () { 171 171 // Generate cached mutable data … … 375 375 IChildWidget[] subWidgets; 376 376 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? 382 class 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 414 431 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; 419 437 } 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; 434 445 } 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 449 506 } 450 507 } 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 529 private: 530 wdim[] pos; // relative position (cumulative width[i-1] plus spacing) 531 wdim spacing; // used by genPositions (which cannot access the layout class's data) 532 // Callbacks used to actually adjust a column's width: 533 void delegate (myIt,wdim,int) setWidthCb[]; // set width of a column, with resize direction 546 534 } 535 536 /************************************************************************************************** 537 * Alignment device 538 * 539 * E.g. can control widths of columns within a grid, and provide sensible resizing, respecting the 540 * minimal width required by each cell in a column. Is not restricted to horizontal widths, but to 541 * ease descriptions, a horizontal context (column widths) is assumed. 542 * 543 * Cells should be of type IChildWidget. 544 * 545 * FIXME: 546 * Cells are not directly interacted with, but minimal widths for each column are passed, and 547 * callback functions are used to adjust the width of any column. 548 *************************************************************************************************/ 549 //FIXME: how to set cell positions after resizes? 550 /+ FIXME - remove position information from here, removing need of horizontal alignment of cells 551 into columns. On resizing, don't permanently adjust sizes until callback ends (mouse button 552 released). Until then, always readjust from current "permanent" size. +/ 553 class AlignWidths 554 { 555 /** Create an instance. After creation, the number of columns can only be changed by calling 556 * reset. 557 * 558 * After creation, minimal widths should be set for all columns (minWidth) and 559 * setWidths must be called before other functions are used. */ 560 this (myIt columns) { 561 reset (columns); 562 } 563 564 /** Reset all column information (only keep set callbacks). 565 * 566 * Widths should be set after calling, as on creation. */ 567 void reset (myIt columns) { 568 if (columns < 1) 569 throw new GuiException("AlignWidths: created with <1 column"); 570 minWidth = new wdim[columns]; 571 width = null; // enforce calling setWidths after this 572 } 573 574 /** Initialize widths as minimal widths. */ 575 void setWidths () { 576 width = minWidth.dup; 577 } 578 /** Initialize widths from supplied list, checking validity. */ 579 void setWidths (wdim[] data) { 580 // Set to provided data: 581 width = data; 582 // And check sizes are valid: 583 foreach (i, m; minWidth) 584 // if fixed width or width is less than minimum: 585
