]> Shamusworld >> Repos - apple2/blob - src/gui/gui.cpp
d61cfe4e620cd870b143a095754a3aa1d7a229c0
[apple2] / src / gui / gui.cpp
1 //
2 // GUI.CPP
3 //
4 // Graphical User Interface support
5 // by James L. Hammons
6 //
7 // JLH = James L. Hammons <jlhamm@acm.org>
8 //
9 // WHO  WHEN        WHAT
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
14 //
15 // STILL TO DO:
16 //
17 // - Memory leak on quitting with a window active [DONE]
18 // - Multiple window handling [DONE]
19 //
20
21 #include "gui.h"
22 #include "menu.h"                                                               // Element class methods are pulled in here...
23 #include "window.h"
24 #include "button.h"
25 #include "text.h"
26 #include "video.h"
27 #include "apple2.h"
28
29 // Debug support
30 //#define DEBUG_MAIN_LOOP
31
32 // New main screen buffering
33 // This works, but the colors are rendered incorrectly. Also, it seems that there's
34 // fullscreen blitting still going on--dragging the disk is fast at first but then
35 // gets painfully slow. Not sure what's going on there.
36 #define USE_NEW_MAINBUFFERING
37
38 //#ifdef DEBUG_MAIN_LOOP
39 #include "log.h"
40 //#endif
41
42 /*
43 Work flow: Draw floppy drive.
44 If disk in drive, MO shows eject graphic, otherwise show load graphic.
45 If hit 'new blank image':
46         If disk in drive, ask if want to save if modified
47         else, load it
48
49 */
50
51
52 GUI::GUI(SDL_Surface * surface): menuItem(new MenuItems())
53 {
54         Element::SetScreen(surface);
55 //      windowList.push_back(new Menu());
56
57 // Create drive windows, and config windows here...
58         windowList.push_back(new Window(30, 30, 200, 100));
59         windowList.push_back(new Window(30, 140, 200, 100));
60         windowList.push_back(new Button(30, 250, "Click!"));
61         windowList.push_back(new Text(30, 20, floppyDrive.GetImageName(0)));
62         windowList.push_back(new Text(30, 130, floppyDrive.GetImageName(1)));
63 }
64
65 GUI::~GUI()
66 {
67         // Clean up menuItem, if any
68
69         if (menuItem)
70                 delete menuItem;
71
72         // Clean up the rest
73
74         for(std::list<Element *>::iterator i=windowList.begin(); i!=windowList.end(); i++)
75                 if (*i)
76                         delete *i;
77 }
78
79 void GUI::AddMenuTitle(const char * title)
80 {
81         menuItem->title = title;
82         menuItem->item.clear();
83 }
84
85 void GUI::AddMenuItem(const char * item, Element * (* a)(void)/*= NULL*/, SDLKey k/*= SDLK_UNKNOWN*/)
86 {
87         menuItem->item.push_back(NameAction(item, a, k));
88 }
89
90 void GUI::CommitItemsToMenu(void)
91 {
92 //We could just do a simple check here to see if more than one item is in the list,
93 //and if so fail. Make it so you build the menu first before allowing any other action. [DONE]
94
95 //Right now, we just silently fail...
96         if (windowList.size() > 1)
97         {
98                 WriteLog("GUI: Can't find menu--more than one item in windowList!\n");
99                 return;
100         }
101
102         ((Menu *)(*windowList.begin()))->Add(*menuItem);
103 }
104
105 void GUI::Run(void)
106 {
107         exitGUI = false;
108         showMouse = true;
109         SDL_Event event;
110         std::list<Element *>::iterator i;
111
112         SDL_EnableKeyRepeat(150, 75);
113
114         // Initial update... [Now handled correctly in the constructor]
115         // Uh, still needed here, though... Only makes sense that it should
116         for(i=windowList.begin(); i!=windowList.end(); i++)
117                 (*i)->Draw();
118
119 #ifndef USE_NEW_MAINBUFFERING
120         RenderScreenBuffer();
121 #else
122         FlipMainScreen();
123 #endif
124
125         // Main loop
126         while (!exitGUI)
127         {
128 //              if (SDL_PollEvent(&event))
129                 if (SDL_WaitEvent(&event))
130                 {
131 #ifdef DEBUG_MAIN_LOOP
132 WriteLog("An event was found!");
133 #endif
134                         if (event.type == SDL_USEREVENT)
135                         {
136 #ifdef DEBUG_MAIN_LOOP
137 WriteLog(" -- SDL_USEREVENT\n");
138 #endif
139 //Mebbe add another user event for screen refresh? Why not!
140                                 if (event.user.code == WINDOW_CLOSE)
141                                 {
142                                         for(i=windowList.begin(); i!=windowList.end(); i++)
143                                         {
144                                                 if (*i == (Element *)event.user.data1)
145                                                 {
146                                                         delete *i;
147                                                         windowList.erase(i);
148                                                         break;
149                                                 }
150                                         }
151                                 }
152                                 else if (event.user.code == MENU_ITEM_CHOSEN)
153                                 {
154                                         // Confused? Let me enlighten... What we're doing here is casting
155                                         // data1 as a pointer to a function which returns a Element pointer and
156                                         // which takes no parameters (the "(Element *(*)(void))" part), then
157                                         // derefencing it (the "*" in front of that) in order to call the
158                                         // function that it points to. Clear as mud? Yeah, I hate function
159                                         // pointers too, but what else are you gonna do?
160                                         Element * window = (*(Element *(*)(void))event.user.data1)();
161
162                                         if (window)
163                                                 windowList.push_back(window);
164
165                                         while (SDL_PollEvent(&event));  // Flush the event queue...
166
167                                         event.type = SDL_MOUSEMOTION;
168                                         int mx, my;
169                                         SDL_GetMouseState(&mx, &my);
170                                         event.motion.x = mx, event.motion.y = my;
171                                     SDL_PushEvent(&event);                      // & update mouse position...!
172
173                                         oldMouse.x = mouse.x, oldMouse.y = mouse.y;
174                                         mouse.x = mx, mouse.y = my;             // This prevents "mouse flash"...
175                                 }
176 //There's a *small* problem with the following approach--if a window and a bunch of
177 //child widgets send this message, we'll get a bunch of unnecessary refresh events...
178 //This could be controlled by having the main window refresh itself intelligently...
179
180 //What we could do instead is set a variable in Element and check it after the fact
181 //to see whether or not a refresh is needed.
182 //[This is what we do now.]
183
184 //Dirty rectangle is also possible...
185                                 else if (event.user.code == SCREEN_REFRESH_NEEDED)
186 #ifndef USE_NEW_MAINBUFFERING
187                                         RenderScreenBuffer();
188 #else
189                                         FlipMainScreen();
190 #endif
191                         }
192                         else if (event.type == SDL_ACTIVEEVENT)
193                         {
194 //Need to do a screen refresh here...
195                                 if (event.active.state == SDL_APPMOUSEFOCUS)
196                                         showMouse = (event.active.gain ? true : false);
197
198 #ifndef USE_NEW_MAINBUFFERING
199                                 RenderScreenBuffer();
200 #else
201                                         FlipMainScreen();
202 #endif
203                         }
204                         else if (event.type == SDL_KEYDOWN)
205                         {
206 #ifdef DEBUG_MAIN_LOOP
207 WriteLog(" -- SDL_KEYDOWN\n");
208 #endif
209                                 if (event.key.keysym.sym == SDLK_F1)
210                                         exitGUI = true;
211
212 //Not sure that this is the right way to handle this...
213 //Probably should only give this to the top level window...
214 //                              for(i=windowList.begin(); i!=windowList.end(); i++)
215 //                                      (*i)->HandleKey(event.key.keysym.sym);
216                                 windowList.back()->HandleKey(event.key.keysym.sym);
217                         }
218                         else if (event.type == SDL_MOUSEMOTION)
219                         {
220 #ifdef DEBUG_MAIN_LOOP
221 WriteLog(" -- SDL_MOUSEMOTION\n");
222 #endif
223 //This is for tracking a custom mouse cursor, which we're not doing--YET.
224                                 oldMouse.x = mouse.x, oldMouse.y = mouse.y;
225                                 mouse.x = event.motion.x, mouse.y = event.motion.y;
226
227 //Not sure that this is the right way to handle this...
228 //Right now, we should probably only do mouseover for the last item in the list...
229 //And now we do!
230 //Though, it seems to screw other things up. Maybe it IS better to pass it to all windows?
231 //Or maybe to just the ones that aren't completely obscured?
232 //Probably. Right now, a disk's close button that should be obscured by one sitting on
233 //top of it gets redrawn. Not good. !!! FIX !!!
234                                 for(i=windowList.begin(); i!=windowList.end(); i++)
235                                         (*i)->HandleMouseMove(mouse.x, mouse.y);
236 //                              windowList.back()->HandleMouseMove(mouse.x, mouse.y);
237                         }
238                         else if (event.type == SDL_MOUSEBUTTONDOWN)
239                         {
240 #ifdef DEBUG_MAIN_LOOP
241 WriteLog(" -- SDL_MOUSEBUTTONDOWN\n");
242 #endif
243 //Not sure that this is the right way to handle this...
244 // What we should do here is ensure that whatever has been clicked on gets moved to the
245 // highest priority--in our current data schema that would be the end of the list... !!! FIX !!!
246 //[DONE]
247
248 /*
249
250 We could do the following:
251
252 - Go through list and find which window has been clicked on (if any). If more
253   than one is clicked on, take the one highest in the Z order (closer to the end
254   of the list).
255
256 - If item is highest in Z order, pass click through to window and exit.
257
258 - Otherwise, restore backing store on each window in reverse order.
259
260 - Remove item clicked on from the list. Put removed item at the end of the list.
261
262 - Go through list and pass click through to each window in the list. Also do a
263   blit to backing store and a Draw() for each window.
264
265 Could also do a check (if not clicked on highest Z window) to see which windows
266 it overlaps and just do restore/redraw for those that overlap. To wit:
267
268 - Create new list containing only those windows that overlap the clicking on window.
269
270 - Go through list and do a blit to backing store and a Draw() for each window.
271
272 - Go through list and pass click through to each window in the list.
273
274 */
275
276 #if 0
277 #if 0
278                                 for(i=windowList.begin(); i!=windowList.end(); i++)
279                                         (*i)->HandleMouseButton(event.button.x, event.button.y, true);
280 #else
281 // We use the 1st algorithm here, since it's simpler. If we need to, we can optimize
282 // to the 2nd...
283
284                                 // Walk backward through the list and see if a window was hit.
285                                 // This will automagically return us the window with the highest Z.
286
287                                 std::list<Element *>::reverse_iterator ri;
288                                 std::list<Element *>::iterator hit;// = windowList.end();
289
290                                 for(ri=windowList.rbegin(); ri!=windowList.rend(); ri++)
291                                 {
292                                         if ((*ri)->Inside(event.button.x, event.button.y))
293                                         {
294                                                 // Here's a bit of STL weirdness: Converting from a reverse
295                                                 // iterator to a regular iterator requires backing the iterator
296                                                 // up a position after grabbing it's base() OR going forward
297                                                 // one position with the reverse iterator before grabbing base().
298                                                 // Ugly, but it gets the job done...
299                                                 hit = (++ri).base();
300                                                 // Put it back where we found it, so the tests following this
301                                                 // don't fail...
302                                                 ri--;
303                                                 break;
304                                         }
305                                 }
306
307                                 // If we hit the highest in the list, then pass the event through
308                                 // to the window for handling. if we hit no windows, then pass the
309                                 // event to all windows. Otherwise, we need to shuffle windows.
310
311 //NOTE: We need to pass the click to all windows regardless of whether they're topmost or not...
312                                 if (ri == windowList.rbegin())
313                                 {
314                                         for(i=windowList.begin(); i!=windowList.end(); i++)
315                                                 (*i)->HandleMouseButton(event.button.x, event.button.y, true);
316                                 }
317                                 else if (ri == windowList.rend())
318                                 {
319                                         for(i=windowList.begin(); i!=windowList.end(); i++)
320                                                 (*i)->HandleMouseButton(event.button.x, event.button.y, true);
321                                 }
322                                 else
323                                 {
324 // - Otherwise, restore backing store on each window in reverse order.
325                                         for(ri=windowList.rbegin(); ri!=windowList.rend(); ri++)
326                                                 (*ri)->RestoreScreenFromBackstore();
327                                         // At this point, the screen has been restored...
328
329 // - Remove item clicked on from the list. Put removed item at the end of the list.
330                                         windowList.push_back(*hit);
331                                         windowList.erase(hit);
332 // - Go through list and pass click through to each window in the list. Also do a
333 //  blit to backing store and a Draw() for each window.
334                                         for(i=windowList.begin(); i!= windowList.end(); i++)
335                                         {
336                                                 // Grab bg into backstore
337                                                 (*i)->SaveScreenToBackstore();
338                                                 // Pass click
339                                                 (*i)->HandleMouseButton(event.button.x, event.button.y, true);
340                                                 // Draw?
341                                                 (*i)->Draw();
342                                         }
343                                 }
344 #endif
345 #endif
346 /*
347 A slightly different way to handle this would be to loop through all windows, compare
348 all those above it to see if they obscure it; if so then subdivide it's update rectangle
349 to eliminate drawing the parts that aren't shown. The beauty of this approach is that
350 you don't have to care what order the windows are drawn in and you don't need to worry
351 about the order of restoring the backing store.
352
353 You *do* still need to determine the Z-order of the windows, in order to get the subdivisions
354 correct, but that's not too terrible.
355
356 Also, when doing a window drag, the coverage lists for all windows have to be regenerated.
357 */
358                                 std::list<Element *>::reverse_iterator ri;
359                                 bool movedWindow = false;
360
361                                 for(ri=windowList.rbegin(); ri!=windowList.rend(); ri++)
362                                 {
363                                         if ((*ri)->Inside(event.button.x, event.button.y))
364                                         {
365                                                 // Remove item clicked on from the list & put removed item at the
366                                                 // end of the list, thus putting the window at the top of the Z
367                                                 // order. But IFF window is not already topmost!
368                                                 if (ri != windowList.rbegin())
369                                                 {
370                                                         windowList.push_back(*ri);
371                                                         // Here's a bit of STL weirdness: Converting from a reverse
372                                                         // iterator to a regular iterator requires backing the iterator
373                                                         // up a position after grabbing it's base() OR going forward
374                                                         // one position with the reverse iterator before grabbing base().
375                                                         // Ugly, but it get the job done...
376                                                         windowList.erase((++ri).base());
377                                                         movedWindow = true;
378                                                 }
379
380                                                 break;
381                                         }
382                                 }
383
384 //Small problem here: we should only pass the *hit* to the topmost window and pass
385 //*misses* to everyone else... Otherwise, you can have overlapping draggable windows
386 //and be able to drag both by clicking on a point that intersects both...
387 //(though that may be an interesting way to handle things!)
388 //The thing is that you want to do it on purpose (like with a special grouping widget)
389 //instead of by accident. So, !!! FIX !!!
390                                 // Pass the click on to all windows
391 //                              for(i=windowList.begin(); i!=windowList.end(); i++)
392 //                                      (*i)->HandleMouseButton(event.button.x, event.button.y, true);
393                                 windowList.back()->HandleMouseButton(event.button.x, event.button.y, true);
394
395 //                              // & bail if nothing changed...
396                                 if (movedWindow)
397 //                                      return;
398 {
399                                 // Check for overlap/build coverage lists [O((n^2)/2) algorithm!]
400 //One way to optimize this would be to only reset coverage lists from the point in
401 //the Z order where the previous window was.
402                                 for(i=windowList.begin(); i!=windowList.end(); i++)
403                                 {
404 //One other little quirk: Probably need to clear the backing store as well!
405 //Not sure...
406                                         (*i)->ResetCoverageList();
407
408                                         // This looks odd, but it's just a consequence of iterator weirdness.
409                                         // Otherwise we could just stick a j+1 in the for loop below. :-P
410                                         std::list<Element *>::iterator j = i;
411                                         j++;
412
413                                         for(; j!=windowList.end(); j++)
414                                                 (*i)->AdjustCoverageList((*j)->GetExtents());
415
416 //                                      (*i)->HandleMouseButton(event.button.x, event.button.y, true);
417                                         (*i)->Draw();
418                                 }
419 }
420                         }
421                         else if (event.type == SDL_MOUSEBUTTONUP)
422                         {
423 #ifdef DEBUG_MAIN_LOOP
424 WriteLog(" -- SDL_MOUSEBUTTONUP\n");
425 #endif
426 //Not sure that this is the right way to handle this...
427                                 for(i=windowList.begin(); i!=windowList.end(); i++)
428                                         (*i)->HandleMouseButton(event.button.x, event.button.y, false);
429 //I think we should only do topmost here...
430 //Or should we???
431 //                              windowList.back()->HandleMouseButton(event.button.x, event.button.y, false);
432                         }
433 #ifdef DEBUG_MAIN_LOOP
434 else
435         WriteLog(" -- Unknown event\n");
436 #endif
437
438                         if (Element::ScreenNeedsRefreshing())
439                         {
440 #ifndef USE_NEW_MAINBUFFERING
441 #ifdef DEBUG_MAIN_LOOP
442 WriteLog("Screen refresh called!\n");
443 #endif
444                                 RenderScreenBuffer();
445                                 Element::ScreenWasRefreshed();
446 #else
447                                 FlipMainScreen();
448                                 Element::ScreenWasRefreshed();
449 #endif
450                         }
451                 }
452 //hm. Works, but slows things way down.
453 //Now we use WaitEvents() instead. Yay!
454 //SDL_Delay(10);
455         }
456
457         SDL_EnableKeyRepeat(0, 0);
458 //      return false;
459 }
460
461 void GUI::Stop(void)
462 {
463         exitGUI = true;
464 }