comparison 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
comparison
equal deleted inserted replaced
112:fe061009029d 113:9824bee909fd
33 import mde.lookup.Options; // miscOpts.L10n callback 33 import mde.lookup.Options; // miscOpts.L10n callback
34 34
35 import tango.core.sync.Mutex; 35 import tango.core.sync.Mutex;
36 import tango.util.log.Log : Log, Logger; 36 import tango.util.log.Log : Log, Logger;
37 import tango.util.container.CircularList; // pop-up draw callbacks 37 import tango.util.container.CircularList; // pop-up draw callbacks
38 import tango.util.container.SortedMap;
38 39
39 private Logger logger; 40 private Logger logger;
40 static this () { 41 static this () {
41 logger = Log.getLogger ("mde.gui.WidgetManager"); 42 logger = Log.getLogger ("mde.gui.WidgetManager");
42 } 43 }
60 */ 61 */
61 this (char[] file) { 62 this (char[] file) {
62 super(file); 63 super(file);
63 64
64 Screen.addDrawable (this); 65 Screen.addDrawable (this);
66 clickCallbacks = new typeof(clickCallbacks);
67 motionCallbacks = new typeof(motionCallbacks);
65 } 68 }
66 69
67 // this() runs during static this(), when imde.input doesn't exist. init() runs later. 70 // this() runs during static this(), when imde.input doesn't exist. init() runs later.
68 void init () { 71 void init () {
69 // Doesn't need a lock - cannot conflict with other class functions. 72 // Doesn't need a lock - cannot conflict with other class functions.
97 scope(exit) mutex.unlock; 100 scope(exit) mutex.unlock;
98 if (child is null) return; 101 if (child is null) return;
99 102
100 // 1. Callbacks have the highest priority recieving events (e.g. a button release) 103 // 1. Callbacks have the highest priority recieving events (e.g. a button release)
101 foreach (dg; clickCallbacks) 104 foreach (dg; clickCallbacks)
102 // See IWidgetManager.addClickCallback's documentation:
103 if (dg (cast(wdabs)cx, cast(wdabs)cy, b, state)) return; 105 if (dg (cast(wdabs)cx, cast(wdabs)cy, b, state)) return;
104 106
105 // 2. Then pop-ups 107 // 2. Then pop-ups
106 IChildWidget widg; 108 static IChildWidget[] removedPopupParents;
109 uint removedPopups = 0;
110 IChildWidget widg; // widget clicked on
107 { 111 {
108 auto i = popups.iterator; 112 auto i = popups.iterator;
109 foreach (popup; i) with (popup) { 113 foreach (popup; i) with (popup) {
110 if (cx < x || cx >= x + w || 114 if (cx < x || cx >= x + w ||
111 cy < y || cy >= y + h) { 115 cy < y || cy >= y + h) {
112 i.remove; 116 i.remove;
113 requestRedraw; 117 requestRedraw;
118 if (removedPopupParents.length <= removedPopups)
119 removedPopupParents.length = removedPopupParents.length * 2 + 4;
120 removedPopupParents[removedPopups++] = parent;
114 } else { 121 } else {
115 widg = widget.getWidget (cast(wdabs)cx,cast(wdabs)cy); 122 widg = widget.getWidget (cast(wdabs)cx,cast(wdabs)cy);
116 break; 123 break;
117 } 124 }
118 } 125 }
131 if (widg.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state) & 1) { 138 if (widg.clickEvent (cast(wdabs)cx,cast(wdabs)cy,b,state) & 1) {
132 keyFocus = widg; 139 keyFocus = widg;
133 imde.input.setLetterCallback (&widg.keyEvent); 140 imde.input.setLetterCallback (&widg.keyEvent);
134 } 141 }
135 } 142 }
143
144 // Tell parents their popups closed (needs to be after clickEvent for PopupMenuWidget)
145 while (removedPopups)
146 removedPopupParents[--removedPopups].popupRemoved;
136 } 147 }
137 148
138 /** For mouse motion events. 149 /** For mouse motion events.
139 * 150 *
140 * Sends the event on to all motion callbacks. */ 151 * Sends the event on to all motion callbacks. */
141 void motionEvent (ushort cx, ushort cy) { 152 void motionEvent (ushort scx, ushort scy) {
142 debug scope (failure) 153 debug scope (failure)
143 logger.warn ("motionEvent: failed!"); 154 logger.warn ("motionEvent: failed!");
144 mutex.lock; 155 mutex.lock;
145 scope(exit) mutex.unlock; 156 scope(exit) mutex.unlock;
146 157 wdabs cx = cast(wdabs) scx, cy = cast(wdabs) scy;
147 foreach (dg; motionCallbacks) 158 foreach (dg; motionCallbacks)
148 dg (cast(wdabs)cx, cast(wdabs)cy); 159 dg (cx, cy);
160
161 IChildWidget ohighlighted = highlighted;
162 foreach (popup; popups) with (popup) {
163 if (cx >= x && cx < x+w && cy >= y && cy < y+h) {
164 highlighted = widget.getWidget (cx,cy);
165 goto foundPopup;
166 }
167 }
168 highlighted = null; // not over a popup
169 foundPopup:
170 if (ohighlighted != highlighted) {
171 if (ohighlighted)
172 ohighlighted.highlight (false);
173 if (highlighted)
174 highlighted.highlight (true);
175 requestRedraw;
176 }
149 } 177 }
150 178
151 179
152 void sizeEvent (int nw, int nh) { // IDrawable function 180 void sizeEvent (int nw, int nh) { // IDrawable function
153 mutex.lock; 181 mutex.lock;
171 IRenderer renderer () { 199 IRenderer renderer () {
172 assert (rend !is null, "WidgetManager.renderer: rend is null"); 200 assert (rend !is null, "WidgetManager.renderer: rend is null");
173 return rend; 201 return rend;
174 } 202 }
175 203
176 /** Place a pop-up widget near px,py. 204 /** Place a pop-up widget (widg) above or below parent.
177 * 205 *
178 * WidgetManager sets its position, draws it, passes it click events and removes it; other 206 * WidgetManager sets its position, draws it, passes it click events and removes it; other
179 * functionality should be handled by the widget's parent. */ 207 * functionality should be handled by the widget's parent.
180 void addPopup (wdabs px, wdabs py, IChildWidget widg) { 208 *
181 ActivePopup popup; 209 * Popups currently should not change their size while active. */
182 with (popup) { 210 void addPopup (IChildWidget parent, IChildWidget widg) {
183 widget = widg; 211 debug assert (parent && widg, "addPopup: null widget");
184 w = widg.width; 212 ActivePopup popup;
185 h = widg.height; 213 popup.parent = parent;
186 x = px + w > this.w ? this.w - w : px; 214 with (popup) {
187 if (x < 0) x = 0; 215 widget = widg;
188 y = py + h > this.h ? this.h - h : py; 216 w = widg.width;
189 if (y < 0) y = 0; 217 h = widg.height;
190 widget.setPosition (x, y); 218 x = parent.xPos; // align on left edge
191 } 219 if (x+w > this.w) x += parent.width - w; // align on right edge
192 popups.prepend (popup); 220 y = parent.yPos + parent.height; // place below
193 } 221 if (y+h > this.h) y = parent.yPos - h; // place above
194 void removePopup (IChildWidget widg) { //FIXME: not optimal (maybe change popups though?) 222 widget.setPosition (x, y);
223 }
224 popups.prepend (popup);
225 requestRedraw;
226 }
227 /+ Not required but possibly useful later. Not optimal.
228 void removePopup (IChildWidget widg) {
195 auto i = popups.iterator; 229 auto i = popups.iterator;
196 foreach (popup; i) { 230 foreach (popup; i) {
197 if (popup.widget is widg) 231 if (popup.widget is widg)
198 i.remove; 232 i.remove;
199 } 233 }
200 requestRedraw; 234 requestRedraw;
201 } 235 }+/
202 236
203 void requestRedraw () { 237 void requestRedraw () {
204 imde.mainSchedule.request(imde.SCHEDULE.DRAW); 238 imde.mainSchedule.request(imde.SCHEDULE.DRAW);
205 } 239 }
206 240
209 } 243 }
210 void addMotionCallback (void delegate(wdabs, wdabs) dg) { 244 void addMotionCallback (void delegate(wdabs, wdabs) dg) {
211 motionCallbacks[dg.ptr] = dg; 245 motionCallbacks[dg.ptr] = dg;
212 } 246 }
213 void removeCallbacks (void* frame) { 247 void removeCallbacks (void* frame) {
214 clickCallbacks.remove(frame); 248 clickCallbacks.removeKey(frame);
215 motionCallbacks.remove(frame); 249 motionCallbacks.removeKey(frame);
216 } 250 }
217 //END IWidgetManager methods 251 //END IWidgetManager methods
218 252
219 protected: 253 protected:
220 /* Second stage of widget loading. 254 /* Second stage of widget loading.
248 } 282 }
249 283
250 private: 284 private:
251 struct ActivePopup { 285 struct ActivePopup {
252 IChildWidget widget; 286 IChildWidget widget;
287 IChildWidget parent;
253 wdabs x,y; 288 wdabs x,y;
254 wdsize w,h; 289 wdsize w,h;
255 } 290 }
256 IRenderer rend; 291 IRenderer rend;
257 CircularList!(ActivePopup) popups;// Pop-up [menus] to draw. First element is top popup. 292 CircularList!(ActivePopup) popups;// Pop-up [menus] to draw. First element is top popup.
258 // callbacks indexed by their frame pointers: 293 // callbacks indexed by their frame pointers. Must support removal of elements in foreach:
259 bool delegate(wdabs cx, wdabs cy, ubyte b, bool state) [void*] clickCallbacks; 294 SortedMap!(void*,bool delegate(wdabs cx, wdabs cy, ubyte b, bool state)) clickCallbacks;
260 void delegate(wdabs cx, wdabs cy) [void*] motionCallbacks; 295 SortedMap!(void*,void delegate(wdabs cx, wdabs cy)) motionCallbacks;
261 IChildWidget keyFocus; // widget receiving keyboard input when non-null 296 IChildWidget keyFocus; // widget receiving keyboard input when non-null
297 IChildWidget highlighted; // NOTE: in some ways should be same as keyFocus
262 } 298 }
263 299
264 300
265 import mde.gui.exception; 301 import mde.gui.exception;
266 import mde.content.Content; // AContent passed to a callback 302 import mde.content.Content; // AContent passed to a callback