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