changeset 117:aba2dd815a1f

Some tweaks to popup events and widgets. Moved gui.mtt to guiDemo.mtt Changed handling of clicks with popups. Made some of the popup widgets use usual from widget data construction.
author Diggory Hardy <diggory.hardy@gmail.com>
date Fri, 26 Dec 2008 12:07:38 +0000
parents 5ee69b3ed9c9
children 9ac208b53582
files data/conf/gui.mtt data/conf/guiDemo.mtt examples/guiDemo.d mde/gui/WidgetManager.d mde/gui/widget/Ifaces.d mde/gui/widget/PopupMenu.d mde/gui/widget/Widget.d mde/gui/widget/createWidget.d mde/imde.d
diffstat 9 files changed, 186 insertions(+), 131 deletions(-) [+]
line wrap: on
line diff
--- a/data/conf/gui.mtt	Sun Dec 21 12:03:50 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-{MT01}
-<char[]|Renderer="Simple">
-<char[]|Design="Working">
-{Working}
-<WidgetData|root={0:[0x4100,0,3,1],1:["bar","opts","bar"]}>
-<WidgetData|bar={0:[0x4100,0,1,3],1:["menu","blank","menu"]}>
-<WidgetData|menu={0:[0x2031,0x4011,0],1:["imde.menu"]}>
-<WidgetData|blank={0:[0x2]}>
-<WidgetData|opts={0:[0x2031,0x4100,4,2,1],1:["Options","optName","optSecs"]}>
-<WidgetData|optSecs={0:[0xE030,4],1:["optSec"]}>
-<WidgetData|optSec={0:[0x4100,0,2,1],1:["optName","optVars"]}>
-<WidgetData|optVars={0:[0xE030,0],1:["optDBox"]}>
-<WidgetData|optDBox={0:[0x4100,1,2,1],1:["optBox","optDesc"]}>
-<WidgetData|optBox={0:[0x4100,1,1,3],1:["optName","optSep","optVal"]}>
-<WidgetData|optName={0:[0x4020, 1, 0xfe8c00]}>
-<WidgetData|optDesc={0:[0x4020, 2, 0xaf6000]}>
-<WidgetData|optVal={0:[0xE030,12],1:["optEnum"]}>
-<WidgetData|optEnum={0:[0x4100,0,1,2],1:["optVal","optName"]}>
-<WidgetData|optSep={0:[0x21, 0xff],1:[" = "]}>
-{Basic}
-<WidgetData|root={0:[0x4100,0,2,1],1:["bar","msg"]}>
-<WidgetData|bar={0:[0x4100,0,1,2],1:["menu","blank"]}>
-<WidgetData|menu={0:[0x2031,0x4011,0],1:["imde.menu"]}>
-<WidgetData|msg={0:[0x21,0x90D970],1:["A string!"]}>
-<WidgetData|blank={0:[0x2]}>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/conf/guiDemo.mtt	Fri Dec 26 12:07:38 2008 +0000
@@ -0,0 +1,24 @@
+{MT01}
+<char[]|Renderer="Simple">
+<char[]|Design="Working">
+{Working}
+<WidgetData|root={0:[0x4100,0,3,1],1:["bar","opts","bar"]}>
+<WidgetData|bar={0:[0x4100,0,1,3],1:["menu","blank","menu"]}>
+<WidgetData|menu={0:[0x2031,0x4011,0],1:["imde.menu","menu0"]}>
+<WidgetData|menu0={0:[0xE032,0],1:["menu1"]}>
+<WidgetData|menu1={0:[0x6033],1:["menu2"]}>
+<WidgetData|menu2={0:[0xE032,0],1:["menu2"]}>
+<WidgetData|blank={0:[0x2]}>
+<WidgetData|opts={0:[0x2031,0x4100,4,2,1],1:["Options","optName","optSecs"]}>
+<WidgetData|optSecs={0:[0xE030,4],1:["optSec"]}>
+<WidgetData|optSec={0:[0x4100,0,2,1],1:["optName","optVars"]}>
+<WidgetData|optVars={0:[0xE030,0],1:["optDBox"]}>
+<WidgetData|optDBox={0:[0x4100,1,2,1],1:["optBox","optDesc"]}>
+<WidgetData|optBox={0:[0x4100,1,1,3],1:["optName","optSep","optVal"]}>
+<WidgetData|optName={0:[0x4020, 1, 0xffffff]}>
+<WidgetData|optDesc={0:[0x4020, 2, 0x999999]}>
+<WidgetData|optVal={0:[0xE030,12],1:["optEnum"]}>
+<WidgetData|optEnum={0:[0x4100,0,1,2],1:["optVal","optName"]}>
+<WidgetData|optSep={0:[0x21, 0xff],1:[" = "]}>
+{Basic}
+<WidgetData|root={0:[0x21,0x90D970],1:["A string!"]}>
--- a/examples/guiDemo.d	Sun Dec 21 12:03:50 2008 +0000
+++ b/examples/guiDemo.d	Fri Dec 26 12:07:38 2008 +0000
@@ -43,7 +43,7 @@
     }
     
     // Set up the gui
-    scope WidgetManager gui = new WidgetManager ("gui");
+    scope WidgetManager gui = new WidgetManager ("guiDemo");
     StageState guiLoad () {   // init func
         gui.init;
         gui.loadDesign();
--- a/mde/gui/WidgetManager.d	Sun Dec 21 12:03:50 2008 +0000
+++ b/mde/gui/WidgetManager.d	Fri Dec 26 12:07:38 2008 +0000
@@ -84,7 +84,7 @@
         synchronized(mutex) {
             if (child)
                 child.draw;
-	    foreach (popup; popups.iterator.reverse)
+	    foreach (popup; popups)
 		popup.widget.draw();
 	}
     }
@@ -93,37 +93,47 @@
     /** For mouse click events.
      *
      * Sends the event on to the relevant windows and all click callbacks. */
-    void clickEvent (ushort cx, ushort cy, ubyte b, bool state) {
+    void clickEvent (ushort usx, ushort usy, ubyte b, bool state) {
         debug scope (failure)
             logger.warn ("clickEvent: failed!");
         mutex.lock;
         scope(exit) mutex.unlock;
         if (child is null) return;
         
+        wdabs cx = cast(wdabs) usx, cy = cast(wdabs) usy;
+        
         // 1. Callbacks have the highest priority recieving events (e.g. a button release)
         foreach (dg; clickCallbacks)
-            if (dg (cast(wdabs)cx, cast(wdabs)cy, b, state)) return;
+            if (dg (cx, cy, b, state)) return;
         
-        // 2. Then pop-ups
-	static IChildWidget[] removedPopupParents;
-	uint removedPopups = 0;
+        // 2. Then pop-ups: close from top, depending on click pos
+        // Note: assumes each evaluated popup's parent is not under another still open popup.
+        // Also assumes popup's parent doesn't have other children in its box.
+        size_t removeTo = popups.length;
+        bool eventDone;		// don't pass clickEvent
         IChildWidget widg;	// widget clicked on
-        {
-            auto i = popups.iterator;
-            foreach (popup; i) with (popup) {
-                if (cx < x || cx >= x + w ||
-                    cy < y || cy >= y + h) {
-                    i.remove;
-                    requestRedraw;
-		    if (removedPopupParents.length <= removedPopups)
-			removedPopupParents.length = removedPopupParents.length * 2 + 4;
-		    removedPopupParents[removedPopups++] = parent;
+        foreach_reverse (i,popup; popups) with (popup) {
+            if (cx < x || cx >= x + w ||
+                cy < y || cy >= y + h) {	// on popup
+                if (parent.onSelf (cx, cy)) {
+                    if (parent.popupParentClick()) removeTo = i;
+                    eventDone = true;
+                    break;
                 } else {
-                    widg = widget.getWidget (cast(wdabs)cx,cast(wdabs)cy);
-                    break;
+                    removeTo = i;
+                    parent.popupClose;
                 }
+            } else {
+                widg = widget.getWidget (cast(wdabs)cx,cast(wdabs)cy);
+                break;
             }
         }
+        if (removeTo < popups.length) {
+            requestRedraw;
+            popups = popups[0..removeTo];
+        }
+        if (eventDone)
+            return;
         
         // 3. Then the main widget tree
         debug assert (cx < child.width && cy < child.height, "WidgetManager: child doesn't cover whole area (code error)");
@@ -140,10 +150,6 @@
 		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.
@@ -159,7 +165,7 @@
             dg (cx, cy);
 	
 	IChildWidget ohighlighted = highlighted;
-	foreach (popup; popups) with (popup) {
+	foreach_reverse (popup; popups) with (popup) {
 	    if (cx >= x && cx < x+w && cy >= y && cy < y+h) {
 		highlighted = widget.getWidget (cx,cy);
 		goto foundPopup;
@@ -201,38 +207,38 @@
         return rend;
     }
     
-    /** 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.
-     *
-     * 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;
+    void addPopup (IChildWidget parnt, IChildWidget widg, int flags = 0) {
+	debug assert (parnt && widg, "addPopup: null widget");
+        if (popups.length >= popupsMem.length)
+            popupsMem.length = popupsMem.length * 2 + 2;
+        with (popupsMem[popups.length]) {
+            parent = parnt;
+            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
+            if (flags & 1) {
+	    	y = parent.yPos;
+            	if (y+h > this.h) y += parent.height - h;
+	    	x = parent.xPos + parent.width;
+	    	if (x+w > this.w) x = parent.xPos - w;
+            } else {
+                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);
+        popups = popupsMem[0..popups.length+1];
 	requestRedraw;
     }
-    /+ 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;
+    void removePopup (IChildWidget parnt) {
+	foreach_reverse (i,popup; popups) {
+	    if (popup.parent is parnt)
+		popups = popups[0..i];
 	}
 	requestRedraw;
-    }+/
+    }
 
     void requestRedraw () {
         imde.mainSchedule.request(imde.SCHEDULE.DRAW);
@@ -257,7 +263,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);
+	popups = null;
         
         child = makeWidget ("root");
         child.setup (0, 3);
@@ -289,7 +295,8 @@
         wdsize w,h;
     }
     IRenderer rend;
-    CircularList!(ActivePopup) popups;// Pop-up [menus] to draw. First element is top popup.
+    ActivePopup[] popups;	// Pop-up [menus] to draw. Last element is top popup.
+    ActivePopup[] popupsMem;	// allocated memory for popups
     // 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;
@@ -374,7 +381,7 @@
             } else
                 reader.read([defaultDesign]);
         } catch (NoFileException) {
-            logger.error ("Unable to load GUI: no config file!");
+            logger.error ("Unable to load GUI: no config file: "~fileName);
             // just return: not a fatal error (so long as the game can run without a GUI!)
         } catch (Exception e) {
             logger.error ("Unable to load GUI: errors parsing config file ("~confDir.getFileName(fileName,PRIORITY.HIGH_LOW)~"):");
--- a/mde/gui/widget/Ifaces.d	Sun Dec 21 12:03:50 2008 +0000
+++ b/mde/gui/widget/Ifaces.d	Fri Dec 26 12:07:38 2008 +0000
@@ -85,12 +85,20 @@
     * provides the possibility of per-window renderers (if desired). */
     IRenderer renderer ();
     
-    /** Add/remove a pop-up widget to be drawn.
+    /** Creates and positions a pop-up widget (widg),
+     *
+     * Set flags = 1 to place widg left or right of parent, otherwise it will be placed above or
+     * below parent.
      *
-     * Place popup as close to x,y as possible, or
-     * place popup next to parent (usually below). */
-    void addPopup (IChildWidget parent, IChildWidget popup);
-    //void removePopup (IChildWidget popup);	Possibly useful later.
+     * WidgetManager sets its position, draws it, and passes it click events. If a click event
+     * occurs which isn't on the popup or it's parent, it is removed (unless a newer
+     * popup is not removed).
+     *
+     * Popups currently should not change their size while active. */
+    void addPopup (IChildWidget parent, IChildWidget popup, int flags = 0);
+    /** Remove the popup added by this parent, and any popups added afterwards.
+     * If parent added multiple popups, this would just remove the top one. */
+    void removePopup (IChildWidget parent);
     
     // User input:
     /** Add a mouse click callback.
@@ -227,16 +235,19 @@
 //END Size and position
     
 //BEGIN Events
-    /** Recursively scan the widget tree to find the widget under (x,y).
+    /** Recursively scan the widget tree to find the widget under (cx,cy).
      *
      * If called on a widget, that widget should assume the location is over itself, and so should
      * either return itself or the result of calling getWidget on the appropriate child widget.
      *
      * In the case of Window this may not be the case; it should check and return null if not under
-     * (x,y).
+     * (cx,cy).
      *
-     * Note: use global coordinates (x,y) not coordinates relative to the widget. */
-    IChildWidget getWidget (wdabs x, wdabs y);
+     * Note: use global coordinates (cx,cy) not coordinates relative to the widget. */
+    IChildWidget getWidget (wdabs cx, wdabs cy);
+    
+    /** Return true if (cx,cy) is on self's box (doesn't matter if actually on a subwidget). */
+    bool onSelf (wdabs cx, wdabs cy);
     
     /** Receive a mouse click event at cx,cy from button b (1-5 correspond to L,M,B, wheel up,down)
      * which is a down-click if state is true.
@@ -260,9 +271,17 @@
      * requestRedraw. */
     void highlight (bool state);
     
-    /** After adding a pop-up widget with mgr.addPopup(this,popupWidget), when that pop-up closes
-     * the manager calls requestRedraw, clickEvent if applicable, and then this function. */
-    void popupRemoved ();
+    /** When a pop-up is closed the manager calls requestRedraw and this function on its parent. */
+    void popupClose ();
+    /** When a click is on the parent of a popup, this function is called instead of the usual
+     * clickEvent.
+     * 
+     * Returns:
+     *	true to close the popup.
+     *
+     * Note: this means the parent can't receive text input without code changes. */
+    bool popupParentClick ();
+    
 //END Events
     
     /** Draw, using the stored values of x and y.
--- a/mde/gui/widget/PopupMenu.d	Sun Dec 21 12:03:50 2008 +0000
+++ b/mde/gui/widget/PopupMenu.d	Fri Dec 26 12:07:38 2008 +0000
@@ -41,8 +41,8 @@
 {
     this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
 	content = c;
-	WDCMinCheck (data, 1,0, content);
-	subWidget = menuContent (mgr, id, data, content);
+	WDCMinCheck (data, 1,1, content);
+	subWidget = mgr.makeWidget (data.strings[0], content);
 	
 	adapter = mgr.renderer.getAdapter;
 	adapter.text = content.toString (1);
@@ -64,9 +64,13 @@
 	return 0;
     }
     
-    override void popupRemoved () {
+    override void popupClose () {
 	pushed = false;
     }
+    override bool popupParentClick () {
+        pushed = false;
+        return true;
+    }
     
     override void draw () {
 	mgr.renderer.drawButton (x,y, w,h, pushed);
@@ -88,19 +92,55 @@
 }
 
 /*************************************************************************************************
- * A function which returns the most appropriate content menu widget.
+ * Widget which pops up a sub-menu based on a content on mouse-over.
  *************************************************************************************************/
-IChildWidget menuContent (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) { if (c is null) throw new ContentException;
+class SubMenuWidget : PopupMenuWidget
+{
+    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+        super (mgr, id, data, c);
+    }
+    
+    override int clickEvent (wdabs, wdabs, ubyte b, bool state) {
+        return 0;
+    }
+    
+    override void highlight (bool state) {
+        if (state && !pushed) {
+            pushed = true;
+            mgr.addPopup (this, subWidget, 1);	// causes redraw
+        }
+    }
+}
+
+/*************************************************************************************************
+ * A function which returns a ContentListWidget or MenuButtonContentWidget.
+ *************************************************************************************************/
+IChildWidget flatMenuContent (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+    if (c is null) throw new ContentException;
     if (cast(IContentList) c)
-	return new MenuContentListWidget(mgr,id,data,c);
+	return new ContentListWidget(mgr,id,data,c);
     else if (cast(EventContent) c)
 	return new MenuButtonContentWidget(mgr,id,data,c);
     else // generic uneditable option
         return new DisplayContentWidget(mgr,id,data,c);
 }
 
-/** A menu content-button, like ButtonContentWidget, but which can be activated with the up-click.
- */
+/*************************************************************************************************
+ * A function which returns a SubMenuWidget or MenuButtonContentWidget.
+ *************************************************************************************************/
+IChildWidget subMenuContent (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
+    if (c is null) throw new ContentException;
+    if (cast(IContentList) c)
+	return new SubMenuWidget(mgr,id,data,c);
+    else if (cast(EventContent) c)
+	return new MenuButtonContentWidget(mgr,id,data,c);
+    else // generic uneditable option
+        return new DisplayContentWidget(mgr,id,data,c);
+}
+
+/*************************************************************************************************
+ * A menu content-button, like ButtonContentWidget, but which can be activated with the up-click.
+ *************************************************************************************************/
 class MenuButtonContentWidget : ATextWidget
 {
     this (IWidgetManager mgr, widgetID id, WidgetData data, IContent c) {
@@ -138,31 +178,3 @@
     EventContent content;
     bool pushed;
 }
-
-/// Similar to layout.ContentListWidget but creates sub-widgets with menuContent.
-class MenuContentListWidget : GridWidget
-{
-    this (IWidgetManager mgr, widgetID id, WidgetData data, IContent content) {
-	cList = cast(IContentList) content;
-	WDCMinCheck (data, 2, 0, cList);
-	
-	cols = 1;
-	if ((rows = cList.list.length) > 0) {
-	    subWidgets.length = rows;
-	    foreach (i, c; cList.list) {
-		subWidgets[i] = menuContent (mgr,id,data,c);
-	    }
-	} else {
-	    rows = 1;
-	    subWidgets = [mgr.makeWidget (id, new ErrorContent ("<empty list>"))];
-	}
-	super (mgr, id, data);
-    }
-    
-    override bool saveChanges () {
-	return false;	// sub-widgets don't have an id
-    }
-    
-private:
-    IContentList cList;
-}
--- a/mde/gui/widget/Widget.d	Sun Dec 21 12:03:50 2008 +0000
+++ b/mde/gui/widget/Widget.d	Fri Dec 26 12:07:38 2008 +0000
@@ -116,6 +116,11 @@
         return this;
     }
     
+    // Should be valid for any widget.
+    override bool onSelf (wdabs cx, wdabs cy) {
+        return cx >= x && cx < x + w && cy >= y && cy < y + h;
+    }
+    
     /* Dummy event method (suitable for all widgets which don't respond to events). */
     override int clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) {
 	return 0;
@@ -129,7 +134,10 @@
     override void highlight (bool state) {}
     
     // Only useful to widgets creating popups.
-    override void popupRemoved () {}
+    override void popupClose () {}
+    override bool popupParentClick () {
+        return true;
+    }
 //END Events
     
     /* Basic draw method: draw the background (all widgets should do this). */
--- a/mde/gui/widget/createWidget.d	Sun Dec 21 12:03:50 2008 +0000
+++ b/mde/gui/widget/createWidget.d	Fri Dec 26 12:07:38 2008 +0000
@@ -105,8 +105,9 @@
     SizableBlank	= 0x2,
     Debug		= 0xF,
     
-    // buttons: 0x10
+    // popup widgets: 0x10
     PopupMenu		= TAKES_CONTENT | 0x11,
+    SubMenu		= TAKES_CONTENT | 0x12,
     
     // labels: 0x20
     ContentLabel	= TAKES_CONTENT | 0x20,
@@ -115,12 +116,15 @@
     // content functions: 0x30
     editContent		= FUNCTION | TAKES_CONTENT | SAFE_RECURSION | 0x30,
     addContent		= FUNCTION | 0x31,
+    flatMenuContent	= FUNCTION | TAKES_CONTENT | SAFE_RECURSION | 0x32,
+    subMenuContent	= FUNCTION | TAKES_CONTENT | 0x33,
     
     // content widgets: 0x40
     DisplayContent	= TAKES_CONTENT | 0x40,
     BoolContent		= TAKES_CONTENT | 0x41,
     AStringContent	= TAKES_CONTENT | 0x42,
     ButtonContent	= TAKES_CONTENT | 0x43,
+    MenuButtonContent	= TAKES_CONTENT | 0x44,
     
     GridLayout		= TAKES_CONTENT | 0x100,
     ContentList		= TAKES_CONTENT | SAFE_RECURSION | 0x110,
@@ -139,14 +143,18 @@
 	"FloatingArea",
 	"addContent",
 	"PopupMenu",
+	"SubMenu",
 	"ContentLabel",
         "DisplayContent",
         "BoolContent",
 	"AStringContent",
 	"ButtonContent",
+	"MenuButtonContent",
 	"GridLayout",
+	"subMenuContent",
 	"ContentList",
-	"editContent"];
+	"editContent",
+	"flatMenuContent"];
 
 /* Generates a binary search algorithm. */
 char[] binarySearch (char[] var, char[][] consts) {
--- a/mde/imde.d	Sun Dec 21 12:03:50 2008 +0000
+++ b/mde/imde.d	Fri Dec 26 12:07:38 2008 +0000
@@ -29,14 +29,16 @@
     quit = (new EventContent("quit")).addCallback ((Content){
 	run = false;
     });
-    a = new EventContent("a");
-    b = new EventContent("b");
-    menu = new ContentList ("menu",[quit,a,b]);
+    menu = new ContentList ("menu",[quit,
+                            new EventContent("a"),
+                            new ContentList("subMenu",[
+                                           new EventContent("b"),
+                                           new EventContent("c")])
+                            ]);
 }
 
 ContentList menu;	/// Root menu for imde
 EventContent quit;	/// A content triggering mde to halt
-EventContent a,b;	/// Dummy items
 
 Scheduler mainSchedule; /// The schedule used by the main loop.