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