]> Shamusworld >> Repos - apple2/blob - src/gui/gui.cpp
Added updating floppy icons.
[apple2] / src / gui / gui.cpp
1 //
2 // GUI.CPP
3 //
4 // Graphical User Interface support
5 // by James Hammons
6 //
7 // JLH = James 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 "diskwindow.h"
27 #include "video.h"
28 #include "apple2.h"
29
30 // Debug support
31 //#define DEBUG_MAIN_LOOP
32
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
38
39 //#ifdef DEBUG_MAIN_LOOP
40 #include "log.h"
41 //#endif
42
43 /*
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
48         else, load it
49 If hit 'swap disks', swap disks.
50 */
51
52
53 GUI::GUI(SDL_Surface * surface): menuItem(new MenuItems())
54 {
55         Element::SetScreen(surface);
56 //      windowList.push_back(new Menu());
57
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));
65 }
66
67
68 GUI::~GUI()
69 {
70         // Clean up menuItem, if any
71
72         if (menuItem)
73                 delete menuItem;
74
75         // Clean up the rest
76
77         for(std::list<Element *>::iterator i=windowList.begin(); i!=windowList.end(); i++)
78                 if (*i)
79                         delete *i;
80 }
81
82
83 void GUI::AddMenuTitle(const char * title)
84 {
85         menuItem->title = title;
86         menuItem->item.clear();
87 }
88
89
90 void GUI::AddMenuItem(const char * item, Element * (* a)(void)/*= NULL*/, SDL_Scancode k/*= SDLK_UNKNOWN*/)
91 {
92         menuItem->item.push_back(NameAction(item, a, k));
93 }
94
95
96 void GUI::CommitItemsToMenu(void)
97 {
98 //We could just do a simple check here to see if more than one item is in the list,
99 //and if so fail. Make it so you build the menu first before allowing any other action. [DONE]
100
101 //Right now, we just silently fail...
102         if (windowList.size() > 1)
103         {
104                 WriteLog("GUI: Can't find menu--more than one item in windowList!\n");
105                 return;
106         }
107
108         ((Menu *)(*windowList.begin()))->Add(*menuItem);
109 }
110
111
112 void GUI::Run(void)
113 {
114         exitGUI = false;
115         showMouse = true;
116         SDL_Event event;
117         std::list<Element *>::iterator i;
118
119 // Not sure what replaces this in SDL2...
120 //      SDL_EnableKeyRepeat(150, 75);
121
122         // Also: Need to pick up backbuffer (for those windows that have them)
123         //       BEFORE drawing...
124
125         // Initial update... [Now handled correctly in the constructor]
126         // Uh, still needed here, though... Only makes sense that it should
127         for(i=windowList.begin(); i!=windowList.end(); i++)
128                 (*i)->Draw();
129
130 #ifndef USE_NEW_MAINBUFFERING
131         RenderScreenBuffer();
132 #else
133         FlipMainScreen();
134 #endif
135
136         // Main loop
137         while (!exitGUI)
138         {
139 //              if (SDL_PollEvent(&event))
140                 if (SDL_WaitEvent(&event))
141                 {
142 #ifdef DEBUG_MAIN_LOOP
143 WriteLog("An event was found!");
144 #endif
145                         if (event.type == SDL_USEREVENT)
146                         {
147 #ifdef DEBUG_MAIN_LOOP
148 WriteLog(" -- SDL_USEREVENT\n");
149 #endif
150 //Mebbe add another user event for screen refresh? Why not!
151                                 if (event.user.code == WINDOW_CLOSE)
152                                 {
153                                         for(i=windowList.begin(); i!=windowList.end(); i++)
154                                         {
155                                                 if (*i == (Element *)event.user.data1)
156                                                 {
157                                                         delete *i;
158                                                         windowList.erase(i);
159                                                         break;
160                                                 }
161                                         }
162                                 }
163                                 else if (event.user.code == MENU_ITEM_CHOSEN)
164                                 {
165                                         // Confused? Let me enlighten... What we're doing here is casting
166                                         // data1 as a pointer to a function which returns a Element pointer and
167                                         // which takes no parameters (the "(Element *(*)(void))" part), then
168                                         // derefencing it (the "*" in front of that) in order to call the
169                                         // function that it points to. Clear as mud? Yeah, I hate function
170                                         // pointers too, but what else are you gonna do?
171                                         Element * window = (*(Element *(*)(void))event.user.data1)();
172
173                                         if (window)
174                                                 windowList.push_back(window);
175
176                                         while (SDL_PollEvent(&event));  // Flush the event queue...
177
178                                         event.type = SDL_MOUSEMOTION;
179                                         int mx, my;
180                                         SDL_GetMouseState(&mx, &my);
181                                         event.motion.x = mx, event.motion.y = my;
182                                     SDL_PushEvent(&event);                      // & update mouse position...!
183
184                                         oldMouse.x = mouse.x, oldMouse.y = mouse.y;
185                                         mouse.x = mx, mouse.y = my;             // This prevents "mouse flash"...
186                                 }
187 //There's a *small* problem with the following approach--if a window and a bunch of
188 //child widgets send this message, we'll get a bunch of unnecessary refresh events...
189 //This could be controlled by having the main window refresh itself intelligently...
190
191 //What we could do instead is set a variable in Element and check it after the fact
192 //to see whether or not a refresh is needed.
193 //[This is what we do now.]
194
195 //Dirty rectangle is also possible...
196                                 else if (event.user.code == SCREEN_REFRESH_NEEDED)
197 #ifndef USE_NEW_MAINBUFFERING
198                                         RenderScreenBuffer();
199 #else
200                                         FlipMainScreen();
201 #endif
202                         }
203 //Not sure what to do here for SDL2...
204 #if 0
205                         else if (event.type == SDL_ACTIVEEVENT)
206                         {
207 //Need to do a screen refresh here...
208                                 if (event.active.state == SDL_APPMOUSEFOCUS)
209                                         showMouse = (event.active.gain ? true : false);
210
211 #ifndef USE_NEW_MAINBUFFERING
212                                 RenderScreenBuffer();
213 #else
214                                 FlipMainScreen();
215 #endif
216                         }
217 #endif
218                         else if (event.type == SDL_KEYDOWN)
219                         {
220 #ifdef DEBUG_MAIN_LOOP
221 WriteLog(" -- SDL_KEYDOWN\n");
222 #endif
223                                 if (event.key.keysym.sym == SDLK_F1)
224                                         exitGUI = true;
225
226 //Not sure that this is the right way to handle this...
227 //Probably should only give this to the top level window...
228 //                              for(i=windowList.begin(); i!=windowList.end(); i++)
229 //                                      (*i)->HandleKey(event.key.keysym.sym);
230                                 windowList.back()->HandleKey(event.key.keysym.scancode);
231                         }
232                         else if (event.type == SDL_MOUSEMOTION)
233                         {
234 #ifdef DEBUG_MAIN_LOOP
235 WriteLog(" -- SDL_MOUSEMOTION\n");
236 #endif
237 //This is for tracking a custom mouse cursor, which we're not doing--YET.
238                                 oldMouse.x = mouse.x, oldMouse.y = mouse.y;
239                                 mouse.x = event.motion.x, mouse.y = event.motion.y;
240
241 //Not sure that this is the right way to handle this...
242 //Right now, we should probably only do mouseover for the last item in the list...
243 //And now we do!
244 //Though, it seems to screw other things up. Maybe it IS better to pass it to all windows?
245 //Or maybe to just the ones that aren't completely obscured?
246 //Probably. Right now, a disk's close button that should be obscured by one sitting on
247 //top of it gets redrawn. Not good. !!! FIX !!!
248                                 for(i=windowList.begin(); i!=windowList.end(); i++)
249                                         (*i)->HandleMouseMove(mouse.x, mouse.y);
250 //                              windowList.back()->HandleMouseMove(mouse.x, mouse.y);
251                         }
252                         else if (event.type == SDL_MOUSEBUTTONDOWN)
253                         {
254 #ifdef DEBUG_MAIN_LOOP
255 WriteLog(" -- SDL_MOUSEBUTTONDOWN\n");
256 #endif
257 //Not sure that this is the right way to handle this...
258 // What we should do here is ensure that whatever has been clicked on gets moved to the
259 // highest priority--in our current data schema that would be the end of the list... !!! FIX !!!
260 //[DONE]
261
262 /*
263
264 We could do the following:
265
266 - Go through list and find which window has been clicked on (if any). If more
267   than one is clicked on, take the one highest in the Z order (closer to the end
268   of the list).
269
270 - If item is highest in Z order, pass click through to window and exit.
271
272 - Otherwise, restore backing store on each window in reverse order.
273
274 - Remove item clicked on from the list. Put removed item at the end of the list.
275
276 - Go through list and pass click through to each window in the list. Also do a
277   blit to backing store and a Draw() for each window.
278
279 Could also do a check (if not clicked on highest Z window) to see which windows
280 it overlaps and just do restore/redraw for those that overlap. To wit:
281
282 - Create new list containing only those windows that overlap the clicking on window.
283
284 - Go through list and do a blit to backing store and a Draw() for each window.
285
286 - Go through list and pass click through to each window in the list.
287
288 */
289
290 #if 0
291 #if 0
292                                 for(i=windowList.begin(); i!=windowList.end(); i++)
293                                         (*i)->HandleMouseButton(event.button.x, event.button.y, true);
294 #else
295 // We use the 1st algorithm here, since it's simpler. If we need to, we can optimize
296 // to the 2nd...
297
298                                 // Walk backward through the list and see if a window was hit.
299                                 // This will automagically return us the window with the highest Z.
300
301                                 std::list<Element *>::reverse_iterator ri;
302                                 std::list<Element *>::iterator hit;// = windowList.end();
303
304                                 for(ri=windowList.rbegin(); ri!=windowList.rend(); ri++)
305                                 {
306                                         if ((*ri)->Inside(event.button.x, event.button.y))
307                                         {
308                                                 // Here's a bit of STL weirdness: Converting from a reverse
309                                                 // iterator to a regular iterator requires backing the iterator
310                                                 // up a position after grabbing it's base() OR going forward
311                                                 // one position with the reverse iterator before grabbing base().
312                                                 // Ugly, but it gets the job done...
313                                                 hit = (++ri).base();
314                                                 // Put it back where we found it, so the tests following this
315                                                 // don't fail...
316                                                 ri--;
317                                                 break;
318                                         }
319                                 }
320
321                                 // If we hit the highest in the list, then pass the event through
322                                 // to the window for handling. if we hit no windows, then pass the
323                                 // event to all windows. Otherwise, we need to shuffle windows.
324
325 //NOTE: We need to pass the click to all windows regardless of whether they're topmost or not...
326                                 if (ri == windowList.rbegin())
327                                 {
328                                         for(i=windowList.begin(); i!=windowList.end(); i++)
329                                                 (*i)->HandleMouseButton(event.button.x, event.button.y, true);
330                                 }
331                                 else if (ri == windowList.rend())
332                                 {
333                                         for(i=windowList.begin(); i!=windowList.end(); i++)
334                                                 (*i)->HandleMouseButton(event.button.x, event.button.y, true);
335                                 }
336                                 else
337                                 {
338 // - Otherwise, restore backing store on each window in reverse order.
339                                         for(ri=windowList.rbegin(); ri!=windowList.rend(); ri++)
340                                                 (*ri)->RestoreScreenFromBackstore();
341                                         // At this point, the screen has been restored...
342
343 // - Remove item clicked on from the list. Put removed item at the end of the list.
344                                         windowList.push_back(*hit);
345                                         windowList.erase(hit);
346 // - Go through list and pass click through to each window in the list. Also do a
347 //  blit to backing store and a Draw() for each window.
348                                         for(i=windowList.begin(); i!= windowList.end(); i++)
349                                         {
350                                                 // Grab bg into backstore
351                                                 (*i)->SaveScreenToBackstore();
352                                                 // Pass click
353                                                 (*i)->HandleMouseButton(event.button.x, event.button.y, true);
354                                                 // Draw?
355                                                 (*i)->Draw();
356                                         }
357                                 }
358 #endif
359 #endif
360 /*
361 A slightly different way to handle this would be to loop through all windows, compare
362 all those above it to see if they obscure it; if so then subdivide it's update rectangle
363 to eliminate drawing the parts that aren't shown. The beauty of this approach is that
364 you don't have to care what order the windows are drawn in and you don't need to worry
365 about the order of restoring the backing store.
366
367 You *do* still need to determine the Z-order of the windows, in order to get the subdivisions
368 correct, but that's not too terrible.
369
370 Also, when doing a window drag, the coverage lists for all windows have to be regenerated.
371 */
372                                 std::list<Element *>::reverse_iterator ri;
373                                 bool movedWindow = false;
374
375                                 for(ri=windowList.rbegin(); ri!=windowList.rend(); ri++)
376                                 {
377                                         if ((*ri)->Inside(event.button.x, event.button.y))
378                                         {
379                                                 // Remove item clicked on from the list & put removed item at the
380                                                 // end of the list, thus putting the window at the top of the Z
381                                                 // order. But IFF window is not already topmost!
382                                                 if (ri != windowList.rbegin())
383                                                 {
384                                                         windowList.push_back(*ri);
385                                                         // Here's a bit of STL weirdness: Converting from a reverse
386                                                         // iterator to a regular iterator requires backing the iterator
387                                                         // up a position after grabbing it's base() OR going forward
388                                                         // one position with the reverse iterator before grabbing base().
389                                                         // Ugly, but it get the job done...
390                                                         windowList.erase((++ri).base());
391                                                         movedWindow = true;
392                                                 }
393
394                                                 break;
395                                         }
396                                 }
397
398 //Small problem here: we should only pass the *hit* to the topmost window and pass
399 //*misses* to everyone else... Otherwise, you can have overlapping draggable windows
400 //and be able to drag both by clicking on a point that intersects both...
401 //(though that may be an interesting way to handle things!)
402 //The thing is that you want to do it on purpose (like with a special grouping widget)
403 //instead of by accident. So, !!! FIX !!!
404                                 // Pass the click on to all windows
405 //                              for(i=windowList.begin(); i!=windowList.end(); i++)
406 //                                      (*i)->HandleMouseButton(event.button.x, event.button.y, true);
407                                 windowList.back()->HandleMouseButton(event.button.x, event.button.y, true);
408
409 //                              // & bail if nothing changed...
410                                 if (movedWindow)
411 //                                      return;
412 {
413                                 // Check for overlap/build coverage lists [O((n^2)/2) algorithm!]
414 //One way to optimize this would be to only reset coverage lists from the point in
415 //the Z order where the previous window was.
416                                 for(i=windowList.begin(); i!=windowList.end(); i++)
417                                 {
418 //One other little quirk: Probably need to clear the backing store as well!
419 //Not sure...
420                                         (*i)->ResetCoverageList();
421
422                                         // This looks odd, but it's just a consequence of iterator weirdness.
423                                         // Otherwise we could just stick a j+1 in the for loop below. :-P
424                                         std::list<Element *>::iterator j = i;
425                                         j++;
426
427                                         for(; j!=windowList.end(); j++)
428                                                 (*i)->AdjustCoverageList((*j)->GetExtents());
429
430 //                                      (*i)->HandleMouseButton(event.button.x, event.button.y, true);
431                                         (*i)->Draw();
432                                 }
433 }
434                         }
435                         else if (event.type == SDL_MOUSEBUTTONUP)
436                         {
437 #ifdef DEBUG_MAIN_LOOP
438 WriteLog(" -- SDL_MOUSEBUTTONUP\n");
439 #endif
440 //Not sure that this is the right way to handle this...
441                                 for(i=windowList.begin(); i!=windowList.end(); i++)
442                                         (*i)->HandleMouseButton(event.button.x, event.button.y, false);
443 //I think we should only do topmost here...
444 //Or should we???
445 //                              windowList.back()->HandleMouseButton(event.button.x, event.button.y, false);
446                         }
447 #ifdef DEBUG_MAIN_LOOP
448 else
449         WriteLog(" -- Unknown event\n");
450 #endif
451
452                         if (Element::ScreenNeedsRefreshing())
453                         {
454 #ifndef USE_NEW_MAINBUFFERING
455 #ifdef DEBUG_MAIN_LOOP
456 WriteLog("Screen refresh called!\n");
457 #endif
458                                 RenderScreenBuffer();
459                                 Element::ScreenWasRefreshed();
460 #else
461                                 FlipMainScreen();
462                                 Element::ScreenWasRefreshed();
463 #endif
464                         }
465                 }
466 //hm. Works, but slows things way down.
467 //Now we use WaitEvents() instead. Yay!
468 //SDL_Delay(10);
469         }
470
471 // Not sure what to do for this in SDL 2...
472 //      SDL_EnableKeyRepeat(0, 0);
473 //      return false;
474 }
475
476
477 void GUI::Stop(void)
478 {
479         exitGUI = true;
480 }
481
482
483
484 //
485 // NEW GUI STARTS HERE
486 //
487
488
489 // Okay, this is ugly but works and I can't think of any better way to handle
490 // this. So what we do when we pass the GIMP bitmaps into a function is pass
491 // them as a (void *) and then cast them as type (Bitmap *) in order to use
492 // them. Yes, it's ugly. Come up with something better!
493
494 struct Bitmap {
495         unsigned int width;
496         unsigned int height;
497         unsigned int bytesPerPixel;                                     // 3:RGB, 4:RGBA
498         unsigned char pixelData[];
499 };
500
501
502 // Icons, in GIMP "C" format
503 #include "gfx/icon-selection.c"
504 #include "gfx/disk-icon.c"
505 #include "gfx/disk-1-icon.c"
506 #include "gfx/disk-2-icon.c"
507 #include "gfx/power-off-icon.c"
508 #include "gfx/power-on-icon.c"
509 #include "gfx/disk-door-open.c"
510 #include "gfx/disk-door-closed.c"
511
512
513 const char numeralOne[(7 * 7) + 1] =
514         "  @@   "
515         " @@@   "
516         "@@@@   "
517         "  @@   "
518         "  @@   "
519         "  @@   "
520         "@@@@@@ ";
521
522 const char numeralTwo[(7 * 7) + 1] =
523         " @@@@@ "
524         "@@   @@"
525         "    @@@"
526         "  @@@@ "
527         " @@@   "
528         "@@     "
529         "@@@@@@@";
530
531 const char ejectIcon[(8 * 7) + 1] =
532         "   @@   "
533         "  @@@@  "
534         " @@@@@@ "
535         "@@@@@@@@"
536         "        "
537         "@@@@@@@@"
538         "@@@@@@@@";
539
540 const char driveLight[(5 * 5) + 1] =
541         " @@@ "
542         "@@@@@"
543         "@@@@@"
544         "@@@@@"
545         " @@@ ";
546
547
548 enum { SBS_SHOWING, SBS_HIDING, SBS_SHOWN, SBS_HIDDEN };
549
550
551 SDL_Texture * GUI2::overlay = NULL;
552 SDL_Rect GUI2::olDst;
553 int GUI2::sidebarState = SBS_HIDDEN;
554 int32_t GUI2::dx = 0;
555 int32_t GUI2::iconSelected = -1;
556 int32_t lastIconSelected = -1;
557 SDL_Texture * iconSelection = NULL;
558 SDL_Texture * diskIcon = NULL;
559 SDL_Texture * disk1Icon = NULL;
560 SDL_Texture * disk2Icon = NULL;
561 SDL_Texture * powerOnIcon = NULL;
562 SDL_Texture * powerOffIcon = NULL;
563 SDL_Texture * doorOpen = NULL;
564 SDL_Texture * doorClosed = NULL;
565 uint32_t texturePointer[128 * 380];
566
567
568 GUI2::GUI2(void)
569 {
570 }
571
572
573 GUI2::~GUI2(void)
574 {
575 }
576
577
578 void GUI2::Init(SDL_Renderer * renderer)
579 {
580         overlay = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888,
581                 SDL_TEXTUREACCESS_TARGET, 128, 380);
582
583         if (!overlay)
584         {
585                 WriteLog("GUI: Could not create overlay!\n");
586                 return;
587         }
588
589         if (SDL_SetTextureBlendMode(overlay, SDL_BLENDMODE_BLEND) == -1)
590                 WriteLog("GUI: Could not set blend mode for overlay.\n");
591
592         for(uint32_t i=0; i<128*380; i++)
593                 texturePointer[i] = 0xB0A000A0;
594
595         SDL_UpdateTexture(overlay, NULL, texturePointer, 128 * sizeof(Uint32));
596
597         olDst.x = VIRTUAL_SCREEN_WIDTH;
598         olDst.y = 2;
599         olDst.w = 128;
600         olDst.h = 380;
601
602         iconSelection = CreateTexture(renderer, &icon_selection);
603         diskIcon      = CreateTexture(renderer, &disk_icon);
604         doorOpen      = CreateTexture(renderer, &door_open);
605         doorClosed    = CreateTexture(renderer, &door_closed);
606         disk1Icon     = CreateTexture(renderer, &disk_1);
607         disk2Icon     = CreateTexture(renderer, &disk_2);
608         powerOffIcon  = CreateTexture(renderer, &power_off);
609         powerOnIcon   = CreateTexture(renderer, &power_on);
610
611         // Set up drive icons in their current states
612         AssembleDriveIcon(renderer, 0);
613         AssembleDriveIcon(renderer, 1);
614
615         if (SDL_SetRenderTarget(renderer, overlay) < 0)
616         {
617                 WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
618         }
619         else
620         {
621                 DrawSidebarIcons(renderer);
622                 // Set render target back to default
623                 SDL_SetRenderTarget(renderer, NULL);
624         }
625
626         WriteLog("GUI: Successfully initialized.\n");
627 }
628
629
630 SDL_Texture * GUI2::CreateTexture(SDL_Renderer * renderer, const void * source)
631 {
632         Bitmap * bitmap = (Bitmap *)source;
633         SDL_Texture * texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888,
634 //              SDL_TEXTUREACCESS_STATIC, bitmap->width, bitmap->height);
635                 SDL_TEXTUREACCESS_TARGET, bitmap->width, bitmap->height);
636         SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
637         SDL_UpdateTexture(texture, NULL, (Uint32 *)bitmap->pixelData,
638                 bitmap->width * sizeof(Uint32));
639
640         return texture;
641 }
642
643
644 void GUI2::MouseDown(int32_t x, int32_t y, uint32_t buttons)
645 {
646 }
647
648
649 void GUI2::MouseUp(int32_t x, int32_t y, uint32_t buttons)
650 {
651 }
652
653
654 void GUI2::MouseMove(int32_t x, int32_t y, uint32_t buttons)
655 {
656         if (sidebarState != SBS_SHOWN)
657         {
658                 iconSelected = -1;
659
660                 if (x > (VIRTUAL_SCREEN_WIDTH - 100))
661                 {
662 //printf("GUI: sidebar showing (x = %i)...\n", x);
663                         sidebarState = SBS_SHOWING;
664                         dx = -8;
665                 }
666                 else
667                 {
668 //printf("GUI: sidebar hiding[1] (x = %i)...\n", x);
669                         sidebarState = SBS_HIDING;
670                         dx = 8;
671                 }
672         }
673         else
674         {
675                 if (x < (VIRTUAL_SCREEN_WIDTH - 100))
676                 {
677                         iconSelected = lastIconSelected = -1;
678                         HandleIconSelection(sdlRenderer);
679 //printf("GUI: sidebar hiding[2] (x = %i)...\n", x);
680                         sidebarState = SBS_HIDING;
681                         dx = 8;
682                 }
683                 // We're in the right zone, and the sidebar is shown, so let's select
684                 // something!
685                 else
686                 {
687                         if (y < 4 || y > 383)
688                         {
689                                 iconSelected = -1;
690                         }
691                         else
692                                 iconSelected = (y - 4) / 54;
693
694                         if (iconSelected != lastIconSelected)
695                         {
696                                 HandleIconSelection(sdlRenderer);
697                                 lastIconSelected = iconSelected;
698                         }
699                 }
700         }
701 }
702
703
704 void GUI2::HandleIconSelection(SDL_Renderer * renderer)
705 {
706         // Set up drive icons in their current states
707         AssembleDriveIcon(renderer, 0);
708         AssembleDriveIcon(renderer, 1);
709
710         // Reload the background...
711         SDL_UpdateTexture(overlay, NULL, texturePointer, 128 * sizeof(Uint32));
712
713         if (SDL_SetRenderTarget(renderer, overlay) < 0)
714         {
715                 WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
716                 return;
717         }
718
719         // Draw the icon selector, if an icon is selected
720         if (iconSelected >= 0)
721         {
722                 SDL_Rect dst;// = { 54, 54, 24 - 7, 2 };
723                 dst.w = dst.h = 54, dst.x = 24 - 7, dst.y = 2 + (iconSelected * 54);
724                 SDL_RenderCopy(renderer, iconSelection, NULL, &dst);
725         }
726
727         DrawSidebarIcons(renderer);
728
729         // Set render target back to default
730         SDL_SetRenderTarget(renderer, NULL);
731 }
732
733
734 void GUI2::AssembleDriveIcon(SDL_Renderer * renderer, int driveNumber)
735 {
736         SDL_Texture * drive[2] = { disk1Icon, disk2Icon };
737         const char * number[2] = { numeralOne, numeralTwo };
738
739         if (SDL_SetRenderTarget(renderer, drive[driveNumber]) < 0)
740         {
741                 WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
742                 return;
743         }
744
745         SDL_RenderClear(renderer);
746         SDL_RenderCopy(renderer, diskIcon, NULL, NULL);
747
748         // Drive door @ (16, 7)
749         SDL_Rect dst;
750         dst.w = 8, dst.h = 10, dst.x = 16, dst.y = 7;
751         SDL_RenderCopy(renderer, (floppyDrive.DriveIsEmpty(driveNumber) ?
752                 doorOpen : doorClosed), NULL, &dst);
753
754         // Numeral @ (30, 20)
755         DrawCharArray(renderer, number[driveNumber], 30, 20, 7, 7, 0xD0, 0xE0, 0xF0);
756         DrawDriveLight(renderer, driveNumber);
757         DrawEjectButton(renderer, driveNumber);
758
759         // Set render target back to default
760         SDL_SetRenderTarget(renderer, NULL);
761 }
762
763
764 void GUI2::DrawEjectButton(SDL_Renderer * renderer, int driveNumber)
765 {
766         if (floppyDrive.DriveIsEmpty(driveNumber))
767                 return;
768
769         DrawCharArray(renderer, ejectIcon, 29, 31, 8, 7, 0x00, 0xAA, 0x00);
770 }
771
772
773 void GUI2::DrawDriveLight(SDL_Renderer * renderer, int driveNumber)
774 {
775         int lightState = floppyDrive.DriveLightStatus(driveNumber);
776         int r = 0x77, g = 0x00, b = 0x00;
777
778         if (lightState == DLS_READ)
779                 r = 0x20, g = 0xFF, b = 0x20;
780         else if (lightState == DLS_WRITE)
781                 r = 0xFF, g = 0x30, b = 0x30;
782
783         // Drive light @ (8, 21)
784         DrawCharArray(renderer, driveLight, 8, 21, 5, 5, r, g, b);
785 }
786
787
788 void GUI2::DrawCharArray(SDL_Renderer * renderer, const char * array, int x,
789         int y, int w, int h, int r, int g, int b)
790 {
791         SDL_SetRenderDrawColor(renderer, r, g, b, 0xFF);
792
793         for(int j=0; j<h; j++)
794         {
795                 for(int i=0; i<w; i++)
796                 {
797                         if (array[(j * w) + i] != ' ')
798                                 SDL_RenderDrawPoint(renderer, x + i, y + j);
799                 }
800         }
801
802         SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00);
803 }
804
805
806 void GUI2::HandleGUIState(void)
807 {
808         olDst.x += dx;
809
810         if (olDst.x < (VIRTUAL_SCREEN_WIDTH - 100) && sidebarState == SBS_SHOWING)
811         {
812                 olDst.x = VIRTUAL_SCREEN_WIDTH - 100;
813 //              sidebarOut = true;
814                 sidebarState = SBS_SHOWN;
815                 dx = 0;
816         }
817         else if (olDst.x > VIRTUAL_SCREEN_WIDTH && sidebarState == SBS_HIDING)
818         {
819                 olDst.x = VIRTUAL_SCREEN_WIDTH;
820                 sidebarState = SBS_HIDDEN;
821                 dx = 0;
822         }
823 }
824
825
826 void GUI2::DrawSidebarIcons(SDL_Renderer * renderer)
827 {
828         SDL_Texture * icons[7] = { powerOnIcon, disk1Icon, disk2Icon, powerOffIcon,
829                 powerOffIcon, powerOffIcon, powerOffIcon };
830
831         SDL_Rect dst;
832         dst.w = dst.h = 40, dst.x = 24, dst.y = 2 + 7;
833
834         for(int i=0; i<7; i++)
835         {
836                 SDL_RenderCopy(renderer, icons[i], NULL, &dst);
837                 dst.y += 54;
838         }
839 }
840
841
842 void GUI2::Render(SDL_Renderer * renderer)
843 {
844         if (!overlay)
845                 return;
846
847         HandleGUIState();
848
849         if (sidebarState != SBS_HIDDEN)
850                 HandleIconSelection(renderer);
851
852         SDL_RenderCopy(renderer, overlay, NULL, &olDst);
853 }
854
855
856 /*
857 GUI Considerations:
858
859 screen is 560 x 384
860
861 cut into 7 pieces give ~54 pix per piece
862 So, let's try 40x40 icons, and see if that's good enough...
863 Selection is 54x54.
864
865 drive proportions: 1.62 : 1
866
867 Icon order:
868
869 +-----+
870 |     |
871 |Power|
872 |     |
873 +-----+
874
875 +-----+
876 |     |
877 |Disk1|
878 |    ^| <-- eject button
879 +-----+
880
881 +-----+
882 |     |
883 |Disk2|
884 |    ^|
885 +-----+
886
887 +-----+
888 |     |
889 |Swap |
890 |     |
891 +-----+
892
893 +-----+
894 |     |
895 |Confg|
896 |     |
897 +-----+
898
899 maybe state save/load
900
901 */
902