# HG changeset patch # User Diggory Hardy # Date 1210844397 -3600 # Node ID 1530d9c04d4dcaf29ddfb6a0e6af63bc2293c9f9 # Parent 8bf53e711cc7201031091607700cb9586f5b3523 Column/row resizing implemented for GridLayoutWidget (finally)! Also new exitImmediately option and a few debug scope(failure) log messages. committer: Diggory Hardy diff -r 8bf53e711cc7 -r 1530d9c04d4d codeDoc/ideas.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/codeDoc/ideas.txt Thu May 15 10:39:57 2008 +0100 @@ -0,0 +1,5 @@ +Miscelaneous ideas for mde. + +Make a special "trace" logger which keeps the last 20, say, trace messages and only output them when asked to do so, which might happen when an exception is caught. It might output them via the normal mechanisms, but only when asked (and not all messages may be in the correct order: trace messages might be logged later than they were added to the list). + +Use debug scope(failure) to output log messages in many places. \ No newline at end of file diff -r 8bf53e711cc7 -r 1530d9c04d4d codeDoc/jobs.txt --- a/codeDoc/jobs.txt Wed May 14 12:31:09 2008 +0100 +++ b/codeDoc/jobs.txt Thu May 15 10:39:57 2008 +0100 @@ -3,17 +3,18 @@ In progress: -Implement row/col sizing. To do (importance 0-5: 0 pointless, 1 no obvious impact now, 2 todo sometime, 3 useful, 4 important, 5 urgent): Also see todo.txt and FIXME/NOTE comment marks. +4 Not guaranteed to catch up-click ending callback! Appears not to be a problem... 4 OutOfMemoryException is not currently checked for − it should be at least in critical places (use high-level catching of all errors?). 3 on-event draw support (mde.events and GUI need to tell mde.mde) 3 Scheduler for drawing only windows which need redrawing. 3 Update scheduler as outlined in FIXME. 3 Windows building/compatibility (currently partial) +2 Options need a "level": simple options, for advanced users, for debugging only, etc. 2 Command-line options for paths to by-pass normal path finding functionality. 2 Consider replacing byte/short types with int type 2 File loading from compressed archives @@ -49,4 +50,3 @@ Done (for git log message): -The renderer now controls which parts of the window border allow resizing. diff -r 8bf53e711cc7 -r 1530d9c04d4d data/L10n/OptionsMisc.mtt --- a/data/L10n/OptionsMisc.mtt Wed May 14 12:31:09 2008 +0100 +++ b/data/L10n/OptionsMisc.mtt Thu May 15 10:39:57 2008 +0100 @@ -3,3 +3,5 @@ + + diff -r 8bf53e711cc7 -r 1530d9c04d4d data/conf/gui.mtt --- a/data/conf/gui.mtt Wed May 14 12:31:09 2008 +0100 +++ b/data/conf/gui.mtt Thu May 15 10:39:57 2008 +0100 @@ -3,7 +3,7 @@ {W1} - + {W2} diff -r 8bf53e711cc7 -r 1530d9c04d4d data/conf/options.mtt --- a/data/conf/options.mtt Wed May 14 12:31:09 2008 +0100 +++ b/data/conf/options.mtt Thu May 15 10:39:57 2008 +0100 @@ -1,8 +1,9 @@ {MT01} {misc} + - + {video} diff -r 8bf53e711cc7 -r 1530d9c04d4d mde/Options.d --- a/mde/Options.d Wed May 14 12:31:09 2008 +0100 +++ b/mde/Options.d Thu May 15 10:39:57 2008 +0100 @@ -341,7 +341,7 @@ /** A home for all miscellaneous options, at least for now. */ OptionsMisc miscOpts; class OptionsMisc : Options { - alias store!("useThreads") BOOL; + alias store!("useThreads", "exitImmediately") BOOL; alias store!("logLevel") INT; alias store!("pollInterval") DOUBLE; alias store!("L10n") CHARA; diff -r 8bf53e711cc7 -r 1530d9c04d4d mde/gui/Gui.d --- a/mde/gui/Gui.d Wed May 14 12:31:09 2008 +0100 +++ b/mde/gui/Gui.d Thu May 15 10:39:57 2008 +0100 @@ -145,6 +145,8 @@ * * Sends the event on to the relevant windows and all click callbacks. */ void clickEvent (ushort cx, ushort cy, ubyte b, bool state) { + debug scope (failure) + logger.warn ("clickEvent: failed!"); // NOTE: buttons receive the up-event even when drag-callbacks are in place. foreach (dg; clickCallbacks) if (dg (cx, cy, b, state)) return; // See IGui.addClickCallback's documentation @@ -165,6 +167,8 @@ * * Sends the event on to all motion callbacks. */ void motionEvent (ushort cx, ushort cy) { + debug scope (failure) + logger.warn ("motionEvent: failed!"); foreach (dg; motionCallbacks) dg (cx, cy); } diff -r 8bf53e711cc7 -r 1530d9c04d4d mde/gui/renderer/IRenderer.d --- a/mde/gui/renderer/IRenderer.d Wed May 14 12:31:09 2008 +0100 +++ b/mde/gui/renderer/IRenderer.d Thu May 15 10:39:57 2008 +0100 @@ -74,6 +74,9 @@ /** Draws a widget background. Usually doesn't do anything since backgrounds are transparent. */ void drawWidgetBack (int x, int y, int w, int h); + /** Draws a blank widget (temporary) */ + void drawBlank (int x, int y, int w, int h); + /** Draws a button frame, in if pushed == true. */ void drawButton (int x, int y, int w, int h, bool pushed); //END draw routines diff -r 8bf53e711cc7 -r 1530d9c04d4d mde/gui/renderer/SimpleRenderer.d --- a/mde/gui/renderer/SimpleRenderer.d Wed May 14 12:31:09 2008 +0100 +++ b/mde/gui/renderer/SimpleRenderer.d Thu May 15 10:39:57 2008 +0100 @@ -93,6 +93,11 @@ void drawWidgetBack (int x, int y, int w, int h) {} + void drawBlank (int x, int y, int w, int h) { + gl.setColor (.4f, .4f, .4f); + gl.drawBox (x,y, w,h); + } + void drawButton (int x, int y, int w, int h, bool pushed) { if (pushed) gl.setColor (1f, 0f, 1f); diff -r 8bf53e711cc7 -r 1530d9c04d4d mde/gui/widget/Widget.d --- a/mde/gui/widget/Widget.d Wed May 14 12:31:09 2008 +0100 +++ b/mde/gui/widget/Widget.d Thu May 15 10:39:57 2008 +0100 @@ -128,6 +128,12 @@ w = (nw >= 0 ? nw : 0); h = (nh >= 0 ? nh : 0); } + + void draw () { + super.draw; + + window.renderer.drawBlank (x,y, w,h); + } } //BEGIN Widgets @@ -138,6 +144,11 @@ if (data.length != 3) throw new WidgetDataException; super (wind, data); } + void draw () { + super.draw; + + window.renderer.drawBlank (x,y, w,h); + } } /// A completely resizable blank widget (initial size zero). @@ -175,14 +186,16 @@ } // Called when a mouse motion/click event occurs while (held == true) bool clickWhileHeld (ushort cx, ushort cy, ubyte b, bool state) { - //NOTE: which button? test - if (cx >= x && cx < x+w && cy >= y && cy < y+h) // button event - Stdout ("Button clicked!").newline; - - pushed = false; - window.requestRedraw; - window.gui.removeCallbacks (cast(void*) this); - + if (b == 1 && state == false) { + if (cx >= x && cx < x+w && cy >= y && cy < y+h) // button event + Stdout ("Button clicked!").newline; + + pushed = false; + window.requestRedraw; + window.gui.removeCallbacks (cast(void*) this); + + return true; + } return false; } void motionWhileHeld (ushort cx, ushort cy) { diff -r 8bf53e711cc7 -r 1530d9c04d4d mde/gui/widget/layout.d --- a/mde/gui/widget/layout.d Wed May 14 12:31:09 2008 +0100 +++ b/mde/gui/widget/layout.d Thu May 15 10:39:57 2008 +0100 @@ -19,6 +19,15 @@ import mde.gui.widget.Widget; import mde.gui.exception; +import tango.io.Stdout; +debug { + import tango.util.log.Log : Log, Logger; + private Logger logger; + static this () { + logger = Log.getLogger ("mde.gui.widget.layout"); + } +} + /** Encapsulates a grid of Widgets. * * Currently there is no support for changing number of cells, sub-widgets or sub-widget properties @@ -58,14 +67,6 @@ foreach (widget; subWidgets) data = widget.adjust (data); - // colWMin/rowHMin are needed as absolute quantities in this function: - int[] cwMin = new int[colWMin.length]; - foreach (i,x; colWMin) - cwMin[i] = x>=0 ? x : -x; - int[] rhMin = new int[rowHMin.length]; - foreach (i,x; rowHMin) - rhMin[i] = x>=0 ? x : -x; - /* We basically short-cut setSize by loading previous col/row sizes and doing the final * calculations. * Note: if setSize gets called afterwards, it should have same dimensions and so not do @@ -73,8 +74,8 @@ int lenUsed = 0; if (data.length < rows + cols) { // data error; use defaults - colW = cwMin; - rowH = rhMin; + colW = colWMin.dup; + rowH = rowHMin.dup; } else { // sufficient data lenUsed = rows+cols; colW = data[0..cols]; @@ -84,9 +85,9 @@ //NOTE: this could be made optional //NOTE: could also check non-resizable sizes are not too large foreach (i, ref w; colW) - if (w < cwMin[i]) w = cwMin[i]; + if (w < colWMin[i]) w = colWMin[i]; foreach (i, ref h; rowH) - if (h < rhMin[i]) h = rhMin[i]; + if (h < rowHMin[i]) h = rowHMin[i]; } genCachedMutableData; @@ -117,18 +118,11 @@ } bool isWSizable () { - // True if any column is resizable: - foreach (d; colWMin) - if (d >= 0) - return true; - return false; + return (sizableCols.length != 0); } bool isHSizable () { - foreach (d; rowHMin) - if (d >= 0) - return true; - return false; + return (sizableRows.length != 0); } /* Calculates the minimal size from all rows and columns of widgets. */ @@ -139,10 +133,10 @@ void setSize (int nw, int nh) { // Step 1: calculate the row/column sizes. - w += adjustCellSizes (colW, colWMin, nw - w, true); - h += adjustCellSizes (rowH, rowHMin, nh - h, true); + w += adjustCellSizes (colW, colWMin, sizableCols, nw - w, true); + h += adjustCellSizes (rowH, rowHMin, sizableRows, nh - h, true); - // Step 2: calculate the row/column offsets (positions) and set the sub-widgets sizes. + // Step 2: calculate the row/column offsets (positions) and set the sub-widget's sizes. genCachedMutableData; // Step 3: position needs to be set @@ -165,7 +159,7 @@ // Find the column int i = cols - 1; // starting from right... while (lx < colX[i]) { // decrement while left of this column - assert (i > 0, "getWidget: left of first column"); // should be impossible + debug assert (i > 0, "getWidget: left of first column"); // should be impossible --i; } // now (lx >= colX[i]) if (lx >= colX[i] + colW[i]) return this; // between columns @@ -173,7 +167,7 @@ // Find the row; int j = rows - 1; while (ly < rowY[j]) { - assert (j > 0, "getWidget: above first row"); // should be impossible + debug assert (j > 0, "getWidget: above first row"); // should be impossible --j; } if (ly >= rowY[j] + rowH[j]) return this; @@ -190,28 +184,59 @@ // Resizing columns & rows void clickEvent (ushort cx, ushort cy, ubyte b, bool state) { + debug scope (failure) + logger.warn ("clickEvent: failure"); if (b == 1 && state == true) { - int l = cx - x; // use relative coords + /* Note: Because of getWidget, this function is only called if the click is not on a + * sub-widget, so we know it's on some divisor (so at least one of resizeCol and + * resizeRow is non-negative). */ // Find the column - resizeCol = cols - 1; // from the right... - while (l < colX[resizeCol]) { // decrement while left of this column - assert (resizeCol > 0, "clickEvent: left of first column"); - --resizeCol; - } // now (l >= colX[resizeCol]) - if (l < colX[resizeCol] + colW[resizeCol]) resizeCol = -1; // on a sub-widget + if (sizableCols.length != 0) { + int l = cx - x; // use relative coords + size_t i = cols - 1; // index, from right + while (l < colX[i]) { // decrement while left of this column + debug assert (i > 0, "clickEvent: left of first column"); + --i; + } // now (l >= colX[resizeCol]) + if (l < colX[i] + colW[i]) i = -1; // on a sub-widget + + // Set resizeColsL / resizeColsH + // Want to find j such that [0..j],[j..$] divide sizableCols about i: + size_t j = 0; + while (j < sizableCols.length && sizableCols[j] <= i) ++j; + + resizeColsL = sizableCols[0..j]; + resizeColsH = sizableCols[j..$]; + + // Cannot resize if either list is empty. resizeCallback checks the length of L, + // but to save it checking R too, we set L's length zero if R's is. + if (resizeColsH.length == 0) + resizeColsL = null; + } // Find the row - resizeRow = rows - 1; - while (l < rowY[resizeRow]) { - assert (resizeRow > 0, "clickEvent: left of first column"); - --resizeRow; + if (sizableRows.length != 0) { + int l = cy - y; + size_t i = rows - 1; + while (l < rowY[i]) { + debug assert (i > 0, "clickEvent: above first row"); + --i; + } + if (l < rowY[i] + rowH[i]) i = -1; + + size_t j = 0; + while (j < sizableRows.length && sizableRows[j] <= i) ++j; + + resizeRowsL = sizableRows[0..j]; + resizeRowsH = sizableRows[j..$]; + + if (resizeRowsH.length == 0) + resizeRowsL = null; } - if (l < rowY[resizeRow] + rowH[resizeRow]) resizeRow = -1; // on a sub-widget - /* Note: Because of getWidget, this function is only called if the click is not on a - * sub-widget, so we know it's on some divisor (so at least one of resizeCol and - * resizeRow is non-negative). */ + if (resizeColsL is null && resizeRowsL is null) + return; // no resizing to do dragX = cx; dragY = cy; @@ -260,26 +285,25 @@ // Find which cols/rows are resizable: + sizableCols = sizableRows = null; // reset; we're about to concatenate to them + forCols: for (uint i = 0; i < cols; ++i) { // for each column for (uint j = 0; j < subWidgets.length; j += cols) // for each row - if (!subWidgets[i+j].isWSizable) { // column not resizable - colWMin[i] = -colWMin[i]; // negate to show not resizable + if (!subWidgets[i+j].isWSizable) // column not resizable continue forCols; // continue the outer for loop - } // column is resizable if we get to here - // entry is left positive, meaning it is resizable + sizableCols ~= i; } forRows: for (uint i = 0; i < subWidgets.length; i += cols) { // for each row for (uint j = 0; j < cols; ++j) // for each column - if (!subWidgets[i+j].isHSizable) { - j = i / cols; // reuse: will be reinitialised next iteration - rowHMin[j] = -rowHMin[j]; + if (!subWidgets[i+j].isHSizable) continue forRows; - } + + sizableRows ~= i / cols; } } @@ -314,50 +338,46 @@ * Params: * cellD = current sizes; is adjusted by the function to new sizes * cellDMin = minimal sizes (see colWMin/rowHMin) + * sizableCells= List of indexes in cellD for cells which are resizable * diff = amount to increase/decrease the total size * startHigh = if true, start resizing from the tail end of cellD, instead of the beginning * * Returns: * The amount adjusted. This may be larger than diff, since cellD is clamped by cellDMin. */ - int adjustCellSizes (ref int[] cellD, ref int[] cellDMin, int diff, bool startHigh) + int adjustCellSizes (ref int[] cellD, ref int[] cellDMin, ref size_t[] sizableCells, int diff, bool startHigh) in {// Could occur if adjust isn't called first, but this would be a code error: assert (cellD !is null, "adjustCellSizes: cellD is null"); } body { - size_t i = (startHigh ? cellD.length-1 : 0); - size_t ic = (startHigh ? -1 : 1); // amount to iterate to next cell + // Cannot resize if no cells are sizable: + if (sizableCells.length == 0) return 0; + + size_t si = (startHigh ? sizableCells.length-1 : 0); // starting index of sizableCells // FIXME: could do with an overhaul if (diff > 0) { // increase size of first resizable cell - do { - if (cellDMin[i] >= 0) { // cell is resizable - cellD[i] += diff; - return diff; - } - i += ic; - } while (i >= 0 && i < cellD.length); - // no resizable cell: revert to original - cellD[(startHigh ? cellD.length-1 : 0)] += diff; + cellD[sizableCells[si]] += diff; return diff; } else if (diff < 0) { // decrease + size_t sc = (startHigh ? -1 : 1); // amount to iterate + size_t ci; // index in cellD int rd = diff; // running diff while (true) { - // NOTE: could do this differently so that enlarged cells can be shrunk - if (cellDMin[i] >= 0) { // i.e., if resizable: - cellD[i] += rd; // decrease this cell's size (but may be too much) - rd = cellD[i] - cellDMin[i]; - if (rd >= 0) // OK; we're done - return diff;// we hit the mark exactly - - // else we decreased it too much! - cellD[i] = cellDMin[i]; - // rd is remainder to decrease by - } + ci = sizableCells[si]; + + cellD[ci] += rd; // decrease this cell's size (but may be too much) + rd = cellD[ci] - cellDMin[ci]; + if (rd >= 0) // OK; we're done + return diff;// we hit the mark exactly - i += (startHigh ? -1 : 1); // next cell - if (i < 0 || i >= cellD.length) // run out of next cells - return diff - rd; // still had rd left to decrease + // else we decreased it too much! + cellD[ci] = cellDMin[ci]; + // rd is remainder to decrease by + + si += sc; // iterate + if (si < 0 || si >= sizableCells.length) // run out of next cells + return diff - rd; // still had rd left to decrease } } else // no adjustment needed @@ -368,30 +388,48 @@ void resizeCallback (ushort cx, ushort cy) { int move; // NOTE: all resizing is relative, unlike in Window - if (resizeCol >= 0) { + if (resizeColsL.length != 0) { move = cx - dragX; - int[] d0 = colW[0..resizeCol]; - int[] d1 = colW[resizeCol..$]; - int[] m0 = colWMin[0..resizeCol]; - int[] m1 = colWMin[resizeCol..$]; // do shrinking first (in case we hit the minimum) // Note: we rely on x[a..b] pointing to the same memory as x - if (move > 0) { - move = -adjustCellSizes (d1, m1, -move, false); - adjustCellSizes (d0, m0, move, true); + if (move < 0) { + move = -adjustCellSizes (colW, colWMin, resizeColsL, move, false); + adjustCellSizes (colW, colWMin, resizeColsH, move, true); } else { - move = -adjustCellSizes (d0, m0, move, true); - adjustCellSizes (d1, m1, move, false); + move = -adjustCellSizes (colW, colWMin, resizeColsH, -move, true); + adjustCellSizes (colW, colWMin, resizeColsL, move, false); } + + dragX = cx; } - // FIXME: vertical + if (resizeRowsL.length != 0) { + move = cy - dragY; + + // do shrinking first (in case we hit the minimum) + // Note: we rely on x[a..b] pointing to the same memory as x + if (move < 0) { + move = -adjustCellSizes (rowH, rowHMin, resizeRowsL, move, false); + adjustCellSizes (rowH, rowHMin, resizeRowsH, move, true); + } else { + move = -adjustCellSizes (rowH, rowHMin, resizeRowsH, -move, true); + adjustCellSizes (rowH, rowHMin, resizeRowsL, move, false); + } + + dragY = cy; + } + + // NOTE: this might be able to be made more efficient, but basically this all needs to happen: + genCachedMutableData; + setPosition (x,y); window.requestRedraw; } bool endCallback (ushort cx, ushort cy, ubyte b, bool state) { if (b == 1 && state == false) { window.gui.removeCallbacks (cast(void*) this); + // Remove unwanted data (although it shouldn't free any memory): + resizeColsL = resizeColsH = resizeRowsL = resizeRowsH = null; return true; // we've handled the up-click } return false; // we haven't handled it @@ -399,27 +437,33 @@ protected: // Data for resizing cols/rows: - int resizeCol = -1, // col/row being resized; - resizeRow = -1; // negative if none int dragX, dragY; // coords where drag starts + // Lists of columns/rows with lower/higher index than the resize position + size_t[] resizeColsL, resizeColsH, resizeRowsL, resizeRowsH; //END Col/row resizing - // Construction data (saved): + + //BEGIN Construction (i.e. non-mutable) data int cols, rows; // number of cells in grid - IWidget[] subWidgets; // all widgets in the grid (by row): - /* SubWidget order: [ 0 1 ] - * [ 2 3 ] */ + + /* All widgets in the grid, by row. Order: [ 0 1 ] + * [ 2 3 ] */ + IWidget[] subWidgets; // Cached data calculated from construction data: - int[] colWMin; // absolute value is minimal column width / row height - int[] rowHMin; // negative if fixed size, entry >= 0 if resizible + // Minimal column width / row height: + int[] colWMin, rowHMin; int mw, mh; // minimal dimensions - // Mutable data (saved): - int[] colW; // column width (widest widget) - int[] rowH; // row height (highest widget in the row) + // All resizable cols/rows, in order: + size_t[] sizableCols, sizableRows; + //END Construction data + + + //BEGIN Mutable data + int[] colW, rowH; // column width / row height (largest size in col/row) // Cached data calculated from construction and mutable data: - int[] colX; // cumulative colW[i-1] + padding (add x to get column's left x-coord) - int[] rowY; // cumulative rowH[i-1] + padding + int[] colX, rowY; // cumulative colW[i-1] + padding (add x to get column's left x-coord) + //END Mutable data } diff -r 8bf53e711cc7 -r 1530d9c04d4d mde/mde.d --- a/mde/mde.d Wed May 14 12:31:09 2008 +0100 +++ b/mde/mde.d Thu May 15 10:39:57 2008 +0100 @@ -54,8 +54,8 @@ return 1; } - if (miscOpts.pollInterval !<= 0.1 || miscOpts.pollInterval !>= 0.0) - Options.setDouble ("misc", "pollInterval", 0.0); + if (miscOpts.pollInterval !<= 1.0 || miscOpts.pollInterval !>= 0.0) + Options.setDouble ("misc", "pollInterval", 0.01); //END Initialisation //BEGIN Main loop setup diff -r 8bf53e711cc7 -r 1530d9c04d4d mde/scheduler/Init.d --- a/mde/scheduler/Init.d Wed May 14 12:31:09 2008 +0100 +++ b/mde/scheduler/Init.d Thu May 15 10:39:57 2008 +0100 @@ -139,8 +139,10 @@ throw new InitException ("Loading options failed: " ~ e.msg); } - // Now re-set the logging level: - Log.getRootLogger.setLevel (cast(Log.Level) miscOpts.logLevel, true); // set the stored log level + // Now re-set the logging level, using the value from the config file: + Log.getRootLogger.setLevel (cast(Log.Level) miscOpts.logLevel, true); + // And set this (debug option): + imde.run = !miscOpts.exitImmediately; //END Pre-init debug logger.trace ("Init: pre-init done");