diff mde/gui/WidgetManager.d @ 113:9824bee909fd

Popup menu; works for simple menus except that clicking an item doesn't close it. Revised popup support a bit; EnumContentWidget is broken and due to be replaced.
author Diggory Hardy <diggory.hardy@gmail.com>
date Fri, 19 Dec 2008 10:32:28 +0000
parents fe061009029d
children b16a534f5302
line wrap: on
line diff
--- a/mde/gui/WidgetManager.d	Sat Dec 13 12:54:43 2008 +0000
+++ b/mde/gui/WidgetManager.d	Fri Dec 19 10:32:28 2008 +0000
@@ -35,6 +35,7 @@
 import tango.core.sync.Mutex;
 import tango.util.log.Log : Log, Logger;
 import tango.util.container.CircularList;	// pop-up draw callbacks
+import tango.util.container.SortedMap;
 
 private Logger logger;
 static this () {
@@ -62,6 +63,8 @@
         super(file);
         
         Screen.addDrawable (this);
+	clickCallbacks = new typeof(clickCallbacks);
+	motionCallbacks = new typeof(motionCallbacks);
     }
     
     // this() runs during static this(), when imde.input doesn't exist. init() runs later.
@@ -99,11 +102,12 @@
         
         // 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;
         
         // 2. Then pop-ups
-        IChildWidget widg;
+	static IChildWidget[] removedPopupParents;
+	uint removedPopups = 0;
+        IChildWidget widg;	// widget clicked on
         {
             auto i = popups.iterator;
             foreach (popup; i) with (popup) {
@@ -111,6 +115,9 @@
                     cy < y || cy >= y + h) {
                     i.remove;
                     requestRedraw;
+		    if (removedPopupParents.length <= removedPopups)
+			removedPopupParents.length = removedPopupParents.length * 2 + 4;
+		    removedPopupParents[removedPopups++] = parent;
                 } else {
                     widg = widget.getWidget (cast(wdabs)cx,cast(wdabs)cy);
                     break;
@@ -133,19 +140,40 @@
 		imde.input.setLetterCallback (&widg.keyEvent);
 	    }
 	}
+	
+	// Tell parents their popups closed (needs to be after clickEvent for PopupMenuWidget)
+	while (removedPopups)
+	    removedPopupParents[--removedPopups].popupRemoved;
     }
     
     /** For mouse motion events.
      *
      * Sends the event on to all motion callbacks. */
-    void motionEvent (ushort cx, ushort cy) {
+    void motionEvent (ushort scx, ushort scy) {
         debug scope (failure)
                 logger.warn ("motionEvent: failed!");
         mutex.lock;
         scope(exit) mutex.unlock;
-        
+	wdabs cx = cast(wdabs) scx, cy = cast(wdabs) scy;
         foreach (dg; motionCallbacks)
-            dg (cast(wdabs)cx, cast(wdabs)cy);
+            dg (cx, cy);
+	
+	IChildWidget ohighlighted = highlighted;
+	foreach (popup; popups) with (popup) {
+	    if (cx >= x && cx < x+w && cy >= y && cy < y+h) {
+		highlighted = widget.getWidget (cx,cy);
+		goto foundPopup;
+	    }
+	}
+	highlighted = null;	// not over a popup
+	foundPopup:
+	if (ohighlighted != highlighted) {
+	    if (ohighlighted)
+		ohighlighted.highlight (false);
+	    if (highlighted)
+		highlighted.highlight (true);
+	    requestRedraw;
+	}
     }
     
     
@@ -173,32 +201,38 @@
         return rend;
     }
     
-    /** Place a pop-up widget near px,py.
+    /** Place a pop-up widget (widg) above or below parent.
      *
      * WidgetManager sets its position, draws it, passes it click events and removes it; other
-     * functionality should be handled by the widget's parent. */
-    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);
+     * functionality should be handled by the widget's parent.
+     *
+     * Popups currently should not change their size while active. */
+    void addPopup (IChildWidget parent, IChildWidget widg) {
+	debug assert (parent && widg, "addPopup: null widget");
+	ActivePopup popup;
+	popup.parent = parent;
+	with (popup) {
+	    widget = widg;
+	    w = widg.width;
+	    h = widg.height;
+	    x = parent.xPos;				// align on left edge
+	    if (x+w > this.w) x += parent.width - w;	// align on right edge
+	    y = parent.yPos + parent.height;		// place below
+	    if (y+h > this.h) y = parent.yPos - h;	// place above
+	    widget.setPosition (x, y);
+	}
+	popups.prepend (popup);
+	requestRedraw;
     }
-    void removePopup (IChildWidget widg) {	//FIXME: not optimal (maybe change popups though?)
+    /+ Not required but possibly useful later. Not optimal.
+    void removePopup (IChildWidget widg) {
 	auto i = popups.iterator;
 	foreach (popup; i) {
 	    if (popup.widget is widg)
 		i.remove;
 	}
 	requestRedraw;
-    }
+    }+/
 
     void requestRedraw () {
         imde.mainSchedule.request(imde.SCHEDULE.DRAW);
@@ -211,8 +245,8 @@
         motionCallbacks[dg.ptr] = dg;
     }
     void removeCallbacks (void* frame) {
-        clickCallbacks.remove(frame);
-        motionCallbacks.remove(frame);
+        clickCallbacks.removeKey(frame);
+        motionCallbacks.removeKey(frame);
     }
     //END IWidgetManager methods
     
@@ -250,15 +284,17 @@
 private:
     struct ActivePopup {
         IChildWidget widget;
+	IChildWidget parent;
         wdabs x,y;
         wdsize w,h;
     }
     IRenderer rend;
     CircularList!(ActivePopup) popups;// Pop-up [menus] to draw. First element is top popup.
-    // 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;
+    // callbacks indexed by their frame pointers. Must support removal of elements in foreach:
+    SortedMap!(void*,bool delegate(wdabs cx, wdabs cy, ubyte b, bool state)) clickCallbacks;
+    SortedMap!(void*,void delegate(wdabs cx, wdabs cy)) motionCallbacks;
     IChildWidget keyFocus;	// widget receiving keyboard input when non-null
+    IChildWidget highlighted;	// NOTE: in some ways should be same as keyFocus
 }