4 // Graphical User Interface support
7 // JLH = James L. Hammons <jlhamm@acm.org>
10 // --- ---------- ------------------------------------------------------------
11 // JLH 02/03/2006 Created this file
12 // JLH 03/13/2006 Added functions to allow shutting down GUI externally
13 // JLH 03/22/2006 Finalized basic multiple window support
17 // - Memory leak on quitting with a window active [DONE]
18 // - Multiple window handling [DONE]
22 #include "menu.h" // Element class methods are pulled in here...
26 #include "diskwindow.h"
31 //#define DEBUG_MAIN_LOOP
33 // New main screen buffering
34 // This works, but the colors are rendered incorrectly. Also, it seems that there's
35 // fullscreen blitting still going on--dragging the disk is fast at first but then
36 // gets painfully slow. Not sure what's going on there.
37 #define USE_NEW_MAINBUFFERING
39 //#ifdef DEBUG_MAIN_LOOP
44 Work flow: Draw floppy drive.
45 If disk in drive, MO shows eject graphic, otherwise show load graphic.
46 If hit 'new blank image':
47 If disk in drive, ask if want to save if modified
49 If hit 'swap disks', swap disks.
53 GUI::GUI(SDL_Surface * surface): menuItem(new MenuItems())
55 Element::SetScreen(surface);
56 // windowList.push_back(new Menu());
58 // Create drive windows, and config windows here...
59 windowList.push_back(new Window(30, 30, 200, 100));
60 windowList.push_back(new Window(30, 140, 200, 100));
61 windowList.push_back(new Button(30, 250, "Click!"));
62 windowList.push_back(new Text(30, 20, floppyDrive.GetImageName(0)));
63 windowList.push_back(new Text(30, 130, floppyDrive.GetImageName(1)));
64 windowList.push_back(new DiskWindow(&floppyDrive, 240, 20));
69 // Clean up menuItem, if any
76 for(std::list<Element *>::iterator i=windowList.begin(); i!=windowList.end(); i++)
81 void GUI::AddMenuTitle(const char * title)
83 menuItem->title = title;
84 menuItem->item.clear();
87 void GUI::AddMenuItem(const char * item, Element * (* a)(void)/*= NULL*/, SDLKey k/*= SDLK_UNKNOWN*/)
89 menuItem->item.push_back(NameAction(item, a, k));
92 void GUI::CommitItemsToMenu(void)
94 //We could just do a simple check here to see if more than one item is in the list,
95 //and if so fail. Make it so you build the menu first before allowing any other action. [DONE]
97 //Right now, we just silently fail...
98 if (windowList.size() > 1)
100 WriteLog("GUI: Can't find menu--more than one item in windowList!\n");
104 ((Menu *)(*windowList.begin()))->Add(*menuItem);
112 std::list<Element *>::iterator i;
114 SDL_EnableKeyRepeat(150, 75);
116 // Initial update... [Now handled correctly in the constructor]
117 // Uh, still needed here, though... Only makes sense that it should
118 for(i=windowList.begin(); i!=windowList.end(); i++)
121 #ifndef USE_NEW_MAINBUFFERING
122 RenderScreenBuffer();
130 // if (SDL_PollEvent(&event))
131 if (SDL_WaitEvent(&event))
133 #ifdef DEBUG_MAIN_LOOP
134 WriteLog("An event was found!");
136 if (event.type == SDL_USEREVENT)
138 #ifdef DEBUG_MAIN_LOOP
139 WriteLog(" -- SDL_USEREVENT\n");
141 //Mebbe add another user event for screen refresh? Why not!
142 if (event.user.code == WINDOW_CLOSE)
144 for(i=windowList.begin(); i!=windowList.end(); i++)
146 if (*i == (Element *)event.user.data1)
154 else if (event.user.code == MENU_ITEM_CHOSEN)
156 // Confused? Let me enlighten... What we're doing here is casting
157 // data1 as a pointer to a function which returns a Element pointer and
158 // which takes no parameters (the "(Element *(*)(void))" part), then
159 // derefencing it (the "*" in front of that) in order to call the
160 // function that it points to. Clear as mud? Yeah, I hate function
161 // pointers too, but what else are you gonna do?
162 Element * window = (*(Element *(*)(void))event.user.data1)();
165 windowList.push_back(window);
167 while (SDL_PollEvent(&event)); // Flush the event queue...
169 event.type = SDL_MOUSEMOTION;
171 SDL_GetMouseState(&mx, &my);
172 event.motion.x = mx, event.motion.y = my;
173 SDL_PushEvent(&event); // & update mouse position...!
175 oldMouse.x = mouse.x, oldMouse.y = mouse.y;
176 mouse.x = mx, mouse.y = my; // This prevents "mouse flash"...
178 //There's a *small* problem with the following approach--if a window and a bunch of
179 //child widgets send this message, we'll get a bunch of unnecessary refresh events...
180 //This could be controlled by having the main window refresh itself intelligently...
182 //What we could do instead is set a variable in Element and check it after the fact
183 //to see whether or not a refresh is needed.
184 //[This is what we do now.]
186 //Dirty rectangle is also possible...
187 else if (event.user.code == SCREEN_REFRESH_NEEDED)
188 #ifndef USE_NEW_MAINBUFFERING
189 RenderScreenBuffer();
194 else if (event.type == SDL_ACTIVEEVENT)
196 //Need to do a screen refresh here...
197 if (event.active.state == SDL_APPMOUSEFOCUS)
198 showMouse = (event.active.gain ? true : false);
200 #ifndef USE_NEW_MAINBUFFERING
201 RenderScreenBuffer();
206 else if (event.type == SDL_KEYDOWN)
208 #ifdef DEBUG_MAIN_LOOP
209 WriteLog(" -- SDL_KEYDOWN\n");
211 if (event.key.keysym.sym == SDLK_F1)
214 //Not sure that this is the right way to handle this...
215 //Probably should only give this to the top level window...
216 // for(i=windowList.begin(); i!=windowList.end(); i++)
217 // (*i)->HandleKey(event.key.keysym.sym);
218 windowList.back()->HandleKey(event.key.keysym.sym);
220 else if (event.type == SDL_MOUSEMOTION)
222 #ifdef DEBUG_MAIN_LOOP
223 WriteLog(" -- SDL_MOUSEMOTION\n");
225 //This is for tracking a custom mouse cursor, which we're not doing--YET.
226 oldMouse.x = mouse.x, oldMouse.y = mouse.y;
227 mouse.x = event.motion.x, mouse.y = event.motion.y;
229 //Not sure that this is the right way to handle this...
230 //Right now, we should probably only do mouseover for the last item in the list...
232 //Though, it seems to screw other things up. Maybe it IS better to pass it to all windows?
233 //Or maybe to just the ones that aren't completely obscured?
234 //Probably. Right now, a disk's close button that should be obscured by one sitting on
235 //top of it gets redrawn. Not good. !!! FIX !!!
236 for(i=windowList.begin(); i!=windowList.end(); i++)
237 (*i)->HandleMouseMove(mouse.x, mouse.y);
238 // windowList.back()->HandleMouseMove(mouse.x, mouse.y);
240 else if (event.type == SDL_MOUSEBUTTONDOWN)
242 #ifdef DEBUG_MAIN_LOOP
243 WriteLog(" -- SDL_MOUSEBUTTONDOWN\n");
245 //Not sure that this is the right way to handle this...
246 // What we should do here is ensure that whatever has been clicked on gets moved to the
247 // highest priority--in our current data schema that would be the end of the list... !!! FIX !!!
252 We could do the following:
254 - Go through list and find which window has been clicked on (if any). If more
255 than one is clicked on, take the one highest in the Z order (closer to the end
258 - If item is highest in Z order, pass click through to window and exit.
260 - Otherwise, restore backing store on each window in reverse order.
262 - Remove item clicked on from the list. Put removed item at the end of the list.
264 - Go through list and pass click through to each window in the list. Also do a
265 blit to backing store and a Draw() for each window.
267 Could also do a check (if not clicked on highest Z window) to see which windows
268 it overlaps and just do restore/redraw for those that overlap. To wit:
270 - Create new list containing only those windows that overlap the clicking on window.
272 - Go through list and do a blit to backing store and a Draw() for each window.
274 - Go through list and pass click through to each window in the list.
280 for(i=windowList.begin(); i!=windowList.end(); i++)
281 (*i)->HandleMouseButton(event.button.x, event.button.y, true);
283 // We use the 1st algorithm here, since it's simpler. If we need to, we can optimize
286 // Walk backward through the list and see if a window was hit.
287 // This will automagically return us the window with the highest Z.
289 std::list<Element *>::reverse_iterator ri;
290 std::list<Element *>::iterator hit;// = windowList.end();
292 for(ri=windowList.rbegin(); ri!=windowList.rend(); ri++)
294 if ((*ri)->Inside(event.button.x, event.button.y))
296 // Here's a bit of STL weirdness: Converting from a reverse
297 // iterator to a regular iterator requires backing the iterator
298 // up a position after grabbing it's base() OR going forward
299 // one position with the reverse iterator before grabbing base().
300 // Ugly, but it gets the job done...
302 // Put it back where we found it, so the tests following this
309 // If we hit the highest in the list, then pass the event through
310 // to the window for handling. if we hit no windows, then pass the
311 // event to all windows. Otherwise, we need to shuffle windows.
313 //NOTE: We need to pass the click to all windows regardless of whether they're topmost or not...
314 if (ri == windowList.rbegin())
316 for(i=windowList.begin(); i!=windowList.end(); i++)
317 (*i)->HandleMouseButton(event.button.x, event.button.y, true);
319 else if (ri == windowList.rend())
321 for(i=windowList.begin(); i!=windowList.end(); i++)
322 (*i)->HandleMouseButton(event.button.x, event.button.y, true);
326 // - Otherwise, restore backing store on each window in reverse order.
327 for(ri=windowList.rbegin(); ri!=windowList.rend(); ri++)
328 (*ri)->RestoreScreenFromBackstore();
329 // At this point, the screen has been restored...
331 // - Remove item clicked on from the list. Put removed item at the end of the list.
332 windowList.push_back(*hit);
333 windowList.erase(hit);
334 // - Go through list and pass click through to each window in the list. Also do a
335 // blit to backing store and a Draw() for each window.
336 for(i=windowList.begin(); i!= windowList.end(); i++)
338 // Grab bg into backstore
339 (*i)->SaveScreenToBackstore();
341 (*i)->HandleMouseButton(event.button.x, event.button.y, true);
349 A slightly different way to handle this would be to loop through all windows, compare
350 all those above it to see if they obscure it; if so then subdivide it's update rectangle
351 to eliminate drawing the parts that aren't shown. The beauty of this approach is that
352 you don't have to care what order the windows are drawn in and you don't need to worry
353 about the order of restoring the backing store.
355 You *do* still need to determine the Z-order of the windows, in order to get the subdivisions
356 correct, but that's not too terrible.
358 Also, when doing a window drag, the coverage lists for all windows have to be regenerated.
360 std::list<Element *>::reverse_iterator ri;
361 bool movedWindow = false;
363 for(ri=windowList.rbegin(); ri!=windowList.rend(); ri++)
365 if ((*ri)->Inside(event.button.x, event.button.y))
367 // Remove item clicked on from the list & put removed item at the
368 // end of the list, thus putting the window at the top of the Z
369 // order. But IFF window is not already topmost!
370 if (ri != windowList.rbegin())
372 windowList.push_back(*ri);
373 // Here's a bit of STL weirdness: Converting from a reverse
374 // iterator to a regular iterator requires backing the iterator
375 // up a position after grabbing it's base() OR going forward
376 // one position with the reverse iterator before grabbing base().
377 // Ugly, but it get the job done...
378 windowList.erase((++ri).base());
386 //Small problem here: we should only pass the *hit* to the topmost window and pass
387 //*misses* to everyone else... Otherwise, you can have overlapping draggable windows
388 //and be able to drag both by clicking on a point that intersects both...
389 //(though that may be an interesting way to handle things!)
390 //The thing is that you want to do it on purpose (like with a special grouping widget)
391 //instead of by accident. So, !!! FIX !!!
392 // Pass the click on to all windows
393 // for(i=windowList.begin(); i!=windowList.end(); i++)
394 // (*i)->HandleMouseButton(event.button.x, event.button.y, true);
395 windowList.back()->HandleMouseButton(event.button.x, event.button.y, true);
397 // // & bail if nothing changed...
401 // Check for overlap/build coverage lists [O((n^2)/2) algorithm!]
402 //One way to optimize this would be to only reset coverage lists from the point in
403 //the Z order where the previous window was.
404 for(i=windowList.begin(); i!=windowList.end(); i++)
406 //One other little quirk: Probably need to clear the backing store as well!
408 (*i)->ResetCoverageList();
410 // This looks odd, but it's just a consequence of iterator weirdness.
411 // Otherwise we could just stick a j+1 in the for loop below. :-P
412 std::list<Element *>::iterator j = i;
415 for(; j!=windowList.end(); j++)
416 (*i)->AdjustCoverageList((*j)->GetExtents());
418 // (*i)->HandleMouseButton(event.button.x, event.button.y, true);
423 else if (event.type == SDL_MOUSEBUTTONUP)
425 #ifdef DEBUG_MAIN_LOOP
426 WriteLog(" -- SDL_MOUSEBUTTONUP\n");
428 //Not sure that this is the right way to handle this...
429 for(i=windowList.begin(); i!=windowList.end(); i++)
430 (*i)->HandleMouseButton(event.button.x, event.button.y, false);
431 //I think we should only do topmost here...
433 // windowList.back()->HandleMouseButton(event.button.x, event.button.y, false);
435 #ifdef DEBUG_MAIN_LOOP
437 WriteLog(" -- Unknown event\n");
440 if (Element::ScreenNeedsRefreshing())
442 #ifndef USE_NEW_MAINBUFFERING
443 #ifdef DEBUG_MAIN_LOOP
444 WriteLog("Screen refresh called!\n");
446 RenderScreenBuffer();
447 Element::ScreenWasRefreshed();
450 Element::ScreenWasRefreshed();
454 //hm. Works, but slows things way down.
455 //Now we use WaitEvents() instead. Yay!
459 SDL_EnableKeyRepeat(0, 0);