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