diff mde/gui/WidgetManager.d @ 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 08651e8a8c51
children 6acd96f8685f
line wrap: on
line diff
--- 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.