# HG changeset patch # User Diggory Hardy # Date 1230293258 0 # Node ID aba2dd815a1f1aff84984630ad7b0094fdebfc6f # Parent 5ee69b3ed9c9f1f39a48581212b4a9946d04c266 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. diff -r 5ee69b3ed9c9 -r aba2dd815a1f data/conf/gui.mtt --- 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} - - -{Working} - - - - - - - - - - - - - - - - - - - diff -r 5ee69b3ed9c9 -r aba2dd815a1f data/conf/guiDemo.mtt --- /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} + + +{Working} + + + + + + + + + + + + + + + + + + diff -r 5ee69b3ed9c9 -r aba2dd815a1f examples/guiDemo.d --- 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(); diff -r 5ee69b3ed9c9 -r aba2dd815a1f mde/gui/WidgetManager.d --- 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)~"):"); diff -r 5ee69b3ed9c9 -r aba2dd815a1f mde/gui/widget/Ifaces.d --- 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. diff -r 5ee69b3ed9c9 -r aba2dd815a1f mde/gui/widget/PopupMenu.d --- 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 (""))]; - } - super (mgr, id, data); - } - - override bool saveChanges () { - return false; // sub-widgets don't have an id - } - -private: - IContentList cList; -} diff -r 5ee69b3ed9c9 -r aba2dd815a1f mde/gui/widget/Widget.d --- 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). */ diff -r 5ee69b3ed9c9 -r aba2dd815a1f mde/gui/widget/createWidget.d --- 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) { diff -r 5ee69b3ed9c9 -r aba2dd815a1f mde/imde.d --- 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.