changeset 108:c9fc2d303178

Added capability for border-less pop-up widgets. Simple pop-up menu. Removed grid-layout spacing (may allow any widget to provide spacing later).
author Diggory Hardy <diggory.hardy@gmail.com>
date Wed, 03 Dec 2008 19:37:32 +0000
parents 20f7d813bb0f
children 2a1428ec5344
files codeDoc/jobs.txt data/conf/gui.mtt mde/content/Items.d mde/gui/WidgetManager.d mde/gui/renderer/IRenderer.d mde/gui/renderer/SimpleRenderer.d mde/gui/widget/Ifaces.d mde/gui/widget/Popup.d mde/gui/widget/createWidget.d
diffstat 9 files changed, 173 insertions(+), 74 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/jobs.txt	Sun Nov 30 17:17:56 2008 +0000
+++ b/codeDoc/jobs.txt	Wed Dec 03 19:37:32 2008 +0000
@@ -12,6 +12,7 @@
 3   Use of dtors - don't rely on them? Or what happens when init throws during creation - relying on undefined behaviour.
 3   glBindTexture not working with non-0 index - perhaps use a higher level graphics library at some point.
 3   Windows building/compatibility (currently partial) - tango/sys/win32/SpecialPath.d
+2   Add callback to miscOpts.L10n reverting the option to it's last value if no file is found and reloading translations.
 2   Remove ability to scan, then load, mergetag sections. Not so necessary with section creator callback and allows "sliding window" type partial buffering. Also remove dataset and force use of section creator callback?
 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.
--- a/data/conf/gui.mtt	Sun Nov 30 17:17:56 2008 +0000
+++ b/data/conf/gui.mtt	Wed Dec 03 19:37:32 2008 +0000
@@ -2,7 +2,9 @@
 <char[]|Renderer="Simple">
 <char[]|Design="Working">
 {Working}
-<WidgetData|root={0:[0xC100,0,3,3],1:["square","blank","square","blank","floating","blank","square","blank","square"]}>
+<WidgetData|root={0:[0xC100,0,2,1],1:["bar","floating"]}>
+<WidgetData|bar={0:[0xC100,0,1,3],1:["menu","blank","quit"]}>
+<WidgetData|menu={0:[0xC011],1:["quit","Menu"]}>
 <WidgetData|square={0:[0x1,6,6]}>
 <WidgetData|blank={0:[0x2]}>
 <WidgetData|opts={0:[0x2031,0xC100,0,2,1],1:["Options","optName","optSecs"]}>
@@ -14,8 +16,8 @@
 <WidgetData|optName={0:[0x4020, 1, 0xfe8c00]}>
 <WidgetData|optDesc={0:[0x4020, 2, 0xaf6000]}>
 <WidgetData|optVal={0:[0x6030]}>
-<WidgetData|optSep={0:[0x21, 0xff],1:["="]}>
-<WidgetData|floating={0:[0x8200,6,14,6],1:["quit","blank","opts"]}>
+<WidgetData|optSep={0:[0x21, 0xff],1:[" = "]}>
+<WidgetData|floating={0:[0x8200,14,6],1:["blank","opts"]}>
 <WidgetData|quit={0:[0x2031,0x4043],1:["imde.quit"]}>
 {Basic}
 <WidgetData|root={0:[0x21,0x90D970],1:["A string!"]}>
--- a/mde/content/Items.d	Sun Nov 30 17:17:56 2008 +0000
+++ b/mde/content/Items.d	Wed Dec 03 19:37:32 2008 +0000
@@ -14,7 +14,7 @@
 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 /**************************************************************************************************
- * A generic way to access content items.
+ * A generic way to access content items. Also loads translations on-demand.
  *************************************************************************************************/
 module mde.content.Items;
 
--- a/mde/gui/WidgetManager.d	Sun Nov 30 17:17:56 2008 +0000
+++ b/mde/gui/WidgetManager.d	Wed Dec 03 19:37:32 2008 +0000
@@ -33,6 +33,7 @@
 
 import tango.core.sync.Mutex;
 import tango.util.log.Log : Log, Logger;
+import tango.util.container.CircularList;	// pop-up draw callbacks
 
 private Logger logger;
 static this () {
@@ -42,8 +43,8 @@
 /*************************************************************************************************
  * The widget manager.
  * 
- * This is responsible for loading and saving an entire gui (although more than one may exist),
- * controlling the rendering device (e.g. the screen or a texture), and providing user input.
+ * This provides a layer on top of WidgetLoader, handling input and rendering. Other functionality
+ * is contained in the super class, to simplify supporting new input/graphics libraries.
  * 
  * Currently mouse coordinates are passed to widgets untranslated. It may make sense to translate
  * them and possibly drop events for some uses, such as if the gui is drawn to a texture.
@@ -73,9 +74,12 @@
     
     /** Draw the gui. */
     void draw() {
-        synchronized(mutex)
+        synchronized(mutex) {
             if (child)
                 child.draw;
+	    foreach (popup; popups.iterator.reverse)
+		popup.widget.draw();
+	}
     }
     
     
@@ -89,19 +93,31 @@
         scope(exit) mutex.unlock;
         if (child is null) return;
         
-        // NOTE: buttons receive the up-event even when drag-callbacks are in place.
+        // 1. Callbacks have the highest priority recieving events (e.g. a button release)
         foreach (dg; clickCallbacks)
             // See IWidgetManager.addClickCallback's documentation:
             if (dg (cast(wdabs)cx, cast(wdabs)cy, b, state)) return;
         
-        // test the click was on the child widget
-        // cx/cy are unsigned, thus >= 0. Widget starts at (0,0)
-        if (cx >= child.width || cy >= child.height) {
-            debug logger.warn ("WidgetManager received click not on child; potentially an error");
-            return;
+        // 2. Then pop-ups
+        IChildWidget widg;
+        {
+            auto i = popups.iterator;
+            foreach (popup; i) with (popup) {
+                if (cx < x || cx >= x + w ||
+                    cy < y || cy >= y + h) {
+                    i.remove;
+                    requestRedraw;
+                } else {
+                    widg = widget.getWidget (cast(wdabs)cx,cast(wdabs)cy);
+                    break;
+                }
+            }
         }
-        IChildWidget widg = child.getWidget (cast(wdabs)cx,cast(wdabs)cy);
-        //debug logger.trace ("Click on {}", widg);
+        
+        // 3. Then the main widget tree
+        debug assert (cx < child.width && cy < child.height, "WidgetManager: child doesn't cover whole area (code error)");
+        if (widg is null)
+            widg = child.getWidget (cast(wdabs)cx,cast(wdabs)cy);
 	if (keyFocus && keyFocus !is widg) {
 	    keyFocus.keyFocusLost;
 	    keyFocus = null;
@@ -153,6 +169,21 @@
         return rend;
     }
     
+    void addPopup (wdabs px, wdabs py, IChildWidget widg) {
+        ActivePopup popup;
+        with (popup) {
+            widget = widg;
+            w = widg.width;
+            h = widg.height;
+            x = px + w > this.w ? this.w - w : px;
+            if (x < 0) x = 0;
+            y = py + h > this.h ? this.h - h : py;
+            if (y < 0) y = 0;
+            widget.setPosition (x, y);
+        }
+        popups.prepend (popup);
+    }
+
     void requestRedraw () {
         imde.mainSchedule.request(imde.SCHEDULE.DRAW);
     }
@@ -176,6 +207,7 @@
         // The renderer needs to be created on the first load, but not after this.
         if (rend is null)
             rend = createRenderer (rendName);
+	popups = new CircularList!(ActivePopup);
         
         child = makeWidget ("root");
         finalize;
@@ -200,9 +232,15 @@
     }
     
 private:
+    struct ActivePopup {
+        IChildWidget widget;
+        wdabs x,y;
+        wdsize w,h;
+    }
     IRenderer rend;
+    CircularList!(ActivePopup) popups;// Pop-up [menus] to draw. First element is top popup.
     wdim w,h;       // area available to the widgets
-    wdim mw,mh;     // minimal area available to the widgets
+    wdim mw,mh;     // minimal area required by widgets (ideally for limiting w,h)
     // callbacks indexed by their frame pointers:
     bool delegate(wdabs cx, wdabs cy, ubyte b, bool state) [void*] clickCallbacks;
     void delegate(wdabs cx, wdabs cy) [void*] motionCallbacks;
@@ -220,11 +258,11 @@
 import mde.setup.paths;
 
 /*************************************************************************************************
-* Contains the code for loading and saving the gui, but not the code for drawing it or handling
-* user input.
-* 
-* This abstract class exists solely for separating out some of the functionality.
-*************************************************************************************************/
+ * Contains the code for loading and saving an entire gui (more than one may exist), but not the
+ * code for drawing it or handling user input.
+ * 
+ * This abstract class exists solely for separating out some of the functionality.
+ *************************************************************************************************/
 abstract scope class WidgetLoader : IWidgetManager
 {
     /** Construct a new widget loader.
@@ -399,56 +437,6 @@
         }
     }
     
-    IChildWidget makeWidget (widgetID id, IContent content = null) {
-        debug (mdeWidgets) logger.trace ("Creating widget \""~id~'"');
-        return createWidget (this, id, curData[id], content);
-    }
-    IChildWidget makeWidget (widgetID id, WidgetData data, IContent content = null) {
-	debug (mdeWidgets) logger.trace ("Creating widget \""~id~'"');
-	return createWidget (this, id, data, content);
-    }
-    
-    /** Runs finalize for all descendants, in a deepest first order. */
-    /* NOTE: The way this function works may seem a little odd, but it's designed to allow
-     * shared alignments to be initialized properly:
-     * 1. all sharing members need to know their children's min size
-     * 2. all sharing members need to add their children's min size to the alignment
-     * 3. all sharing members can only then get their min size
-     * This method will fail if alignment members are not all of the same generation. An alternate
-     * method without this drawback would be to have shared alignments created with a list of
-     * pointers to their members, and once all members have been created the alignment could
-     * initialize itself, first making sure each members' children have been initialized. */
-    void finalize () {
-        IChildWidget[][] descendants;   // first index: depth; is a list of widgets at each depth
-        
-        void recurseChildren (size_t depth, IChildWidget widget) {
-            foreach (child; widget.children)
-                recurseChildren (depth+1, child);
-            
-            if (descendants.length <= depth)
-                descendants.length = depth * 2 + 1;
-            descendants[depth] ~= widget;
-        }
-        
-        recurseChildren (0, child);
-        foreach_reverse (generation; descendants) {
-            foreach (widget; generation)
-                widget.prefinalize;
-            foreach (widget; generation)
-                widget.finalize;
-        }
-    }
-    
-    wdims dimData (widgetID id) {
-        return curData.dims (id);
-    }
-    void setData (widgetID id, WidgetData d) {
-        changes[id] = d;        // also updates WidgetDataSet in data.
-    }
-    void setDimData (widgetID id, wdims d) {
-        changes.setDims(id, d);    // also updates WidgetDataSet in data.
-    }
-    
     /** Second stage of loading the widgets.
     * 
     * loadDesign handles the data; this method needs to:
@@ -463,10 +451,62 @@
     */
     void createRootWidget();
     
+    /** Runs finalize for all descendants, in a deepest first order. */
+    /* NOTE: The way this function works may seem a little odd, but it's designed to allow
+    * shared alignments to be initialized properly:
+    * 1. all sharing members need to know their children's min size
+    * 2. all sharing members need to add their children's min size to the alignment
+    * 3. all sharing members can only then get their min size
+    * This method will fail if alignment members are not all of the same generation. An alternate
+    * method without this drawback would be to have shared alignments created with a list of
+    * pointers to their members, and once all members have been created the alignment could
+    * initialize itself, first making sure each members' children have been initialized. */
+    void finalize () {
+	IChildWidget[][] descendants;   // first index: depth; is a list of widgets at each depth
+	
+	void recurseChildren (size_t depth, IChildWidget widget) {
+	    foreach (child; widget.children)
+		recurseChildren (depth+1, child);
+	    
+	    if (descendants.length <= depth)
+		descendants.length = depth * 2 + 1;
+	    descendants[depth] ~= widget;
+	}
+	
+	recurseChildren (0, child);
+	foreach_reverse (generation; descendants) {
+	    foreach (widget; generation)
+		widget.prefinalize;
+	    foreach (widget; generation)
+		widget.finalize;
+	}
+    }
+    
     /** Called before saving (usually when the GUI is about to be destroyed, although not
-     *  necessarily). */
+    *  necessarily). */
     void preSave () {}
     
+    //BEGIN IWidgetManager methods
+    IChildWidget makeWidget (widgetID id, IContent content = null) {
+        debug (mdeWidgets) logger.trace ("Creating widget \""~id~'"');
+        return createWidget (this, id, curData[id], content);
+    }
+    IChildWidget makeWidget (widgetID id, WidgetData data, IContent content = null) {
+	debug (mdeWidgets) logger.trace ("Creating widget \""~id~'"');
+	return createWidget (this, id, data, content);
+    }
+    
+    wdims dimData (widgetID id) {
+        return curData.dims (id);
+    }
+    void setData (widgetID id, WidgetData d) {
+        changes[id] = d;        // also updates WidgetDataSet in data.
+    }
+    void setDimData (widgetID id, wdims d) {
+        changes.setDims(id, d);    // also updates WidgetDataSet in data.
+    }
+    //END IWidgetManager methods
+    
 protected:
     final char[] fileName;
     char[] defaultDesign;		// The design specified in the file header.
--- a/mde/gui/renderer/IRenderer.d	Sun Nov 30 17:17:56 2008 +0000
+++ b/mde/gui/renderer/IRenderer.d	Wed Dec 03 19:37:32 2008 +0000
@@ -142,7 +142,9 @@
     /** Draw a window border plus background. */
     void drawWindow (Border* border, wdim x, wdim y, wdim w, wdim h);
     
-    /** Draws a widget background. Usually doesn't do anything since backgrounds are transparent. */
+    /** Draws a widget background. Usually doesn't do anything since backgrounds are transparent.
+     *
+     * It used to be required for all widgets to do this, but I lapsed since mostly it's unused. */
     void drawWidgetBack (wdim x, wdim y, wdim w, wdim h);
     
     /** Draws a blank widget (temporary) */
--- a/mde/gui/renderer/SimpleRenderer.d	Sun Nov 30 17:17:56 2008 +0000
+++ b/mde/gui/renderer/SimpleRenderer.d	Wed Dec 03 19:37:32 2008 +0000
@@ -54,7 +54,7 @@
     }
     
     wdim layoutSpacing () {
-        return 4;
+        return 0;
     }
     
     
--- a/mde/gui/widget/Ifaces.d	Sun Nov 30 17:17:56 2008 +0000
+++ b/mde/gui/widget/Ifaces.d	Wed Dec 03 19:37:32 2008 +0000
@@ -89,6 +89,8 @@
     * provides the possibility of per-window renderers (if desired). */
     IRenderer renderer ();
     
+    /** Add/remove a pop-up [menu] to be drawn. */
+    void addPopup (wdabs x, wdabs y, IChildWidget popup);
     
     // User input:
     /** Add a mouse click callback.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/gui/widget/Popup.d	Wed Dec 03 19:37:32 2008 +0000
@@ -0,0 +1,49 @@
+/* LICENSE BLOCK
+Part of mde: a Modular D game-oriented Engine
+Copyright © 2007-2008 Diggory Hardy
+
+This program is free software: you can redistribute it and/or modify it under the terms
+of the GNU General Public License as published by the Free Software Foundation, either
+version 2 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+/// Pop-up widgets.
+module mde.gui.widget.Popup;
+
+import mde.gui.widget.Widget;
+import mde.content.Content;
+
+/** Shows a "pop-up" widget tree when clicked. */
+class PopupButtonWidget : AButtonWidget
+{
+    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent content) {
+	WDCheck (data, 1,2);
+	subWidget = mgr.makeWidget (data.strings[0], content);
+        
+        adapter = mgr.renderer.getAdapter (data.strings[1]);
+        adapter.getDimensions (mw, mh);
+        w = mw;
+        h = mh;
+        super (mgr, id, data);
+    }
+    
+    void activated () {
+        mgr.addPopup (x,y, subWidget);
+    }
+    
+    void draw () {
+        super.draw ();
+        adapter.draw (x,y);
+    }
+    
+protected:
+    IChildWidget subWidget;
+    
+    IRenderer.TextAdapter adapter;
+}
--- a/mde/gui/widget/createWidget.d	Sun Nov 30 17:17:56 2008 +0000
+++ b/mde/gui/widget/createWidget.d	Wed Dec 03 19:37:32 2008 +0000
@@ -30,6 +30,7 @@
 import mde.gui.widget.miscContent;
 import mde.gui.widget.textContent;
 import mde.gui.widget.Floating;
+import mde.gui.widget.Popup;
 import tango.util.log.Log : Log, Logger;
 
 private Logger logger;
@@ -97,6 +98,7 @@
     
     // buttons: 0x10
     Button		= 0x10,
+    PopupButton		= TAKES_CONTENT | PARENT | 0x11,
     
     // labels: 0x20
     ContentLabel	= TAKES_CONTENT | 0x20,
@@ -135,6 +137,7 @@
 	"ButtonContent",
         "editContent",
         "FloatingArea",
+	"PopupButton",
         "GridLayout",
 	"ContentList"];