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