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