Mercurial > projects > mde
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.