]> Shamusworld >> Repos - apple2/blobdiff - src/gui/gui.cpp
Added initial emulator configuration window, cleanup of settings code.
[apple2] / src / gui / gui.cpp
index 6128486a2682bc451b064c375bbf7b09f6ffb7e6..6960a9675cf87f4b8d5916f8a666507069730654 100644 (file)
@@ -3,14 +3,16 @@
 //
 // Graphical User Interface support
 // by James Hammons
+// © 2014-2019 Underground Software
 //
 // JLH = James Hammons <jlhamm@acm.org>
 //
 // WHO  WHEN        WHAT
-// ---  ----------  ------------------------------------------------------------
+// ---  ----------  -----------------------------------------------------------
 // JLH  02/03/2006  Created this file
 // JLH  03/13/2006  Added functions to allow shutting down GUI externally
 // JLH  03/22/2006  Finalized basic multiple window support
+// JLH  03/03/2014  Refactored GUI to use SDL 2, more modern approach as well
 //
 // STILL TO DO:
 //
 //
 
 #include "gui.h"
-#include "menu.h"                                                              // Element class methods are pulled in here...
-#include "window.h"
-#include "button.h"
-#include "text.h"
-#include "diskselector.h"
-#include "diskwindow.h"
-#include "video.h"
+#include <vector>
 #include "apple2.h"
-#include "applevideo.h"
-
-// Debug support
-//#define DEBUG_MAIN_LOOP
-
-// New main screen buffering
-// This works, but the colors are rendered incorrectly. Also, it seems that there's
-// fullscreen blitting still going on--dragging the disk is fast at first but then
-// gets painfully slow. Not sure what's going on there.
-//#define USE_NEW_MAINBUFFERING
-
-//#ifdef DEBUG_MAIN_LOOP
+#include "config.h"
+#include "diskselector.h"
+#include "elements.h"
+#include "floppydrive.h"
+#include "font10pt.h"
 #include "log.h"
-//#endif
-
-/*
-Work flow: Draw floppy drive.
-If disk in drive, MO shows eject graphic, otherwise show load graphic.
-If hit 'new blank image':
-       If disk in drive, ask if want to save if modified
-       else, load it
-If hit 'swap disks', swap disks.
-*/
-
-
-GUI::GUI(SDL_Surface * surface): menuItem(new MenuItems())
-{
-       Element::SetScreen(surface);
-//     windowList.push_back(new Menu());
-
-// Create drive windows, and config windows here...
-       windowList.push_back(new Window(30, 30, 200, 100));
-       windowList.push_back(new Window(30, 140, 200, 100));
-       windowList.push_back(new Button(30, 250, "Click!"));
-       windowList.push_back(new Text(30, 20, floppyDrive.ImageName(0)));
-       windowList.push_back(new Text(30, 130, floppyDrive.ImageName(1)));
-       windowList.push_back(new DiskWindow(&floppyDrive, 240, 20));
-}
-
-
-GUI::~GUI()
-{
-       // Clean up menuItem, if any
-
-       if (menuItem)
-               delete menuItem;
-
-       // Clean up the rest
-
-       for(std::list<Element *>::iterator i=windowList.begin(); i!=windowList.end(); i++)
-               if (*i)
-                       delete *i;
-}
-
-
-void GUI::AddMenuTitle(const char * title)
-{
-       menuItem->title = title;
-       menuItem->item.clear();
-}
-
-
-void GUI::AddMenuItem(const char * item, Element * (* a)(void)/*= NULL*/, SDL_Scancode k/*= SDLK_UNKNOWN*/)
-{
-       menuItem->item.push_back(NameAction(item, a, k));
-}
-
-
-void GUI::CommitItemsToMenu(void)
-{
-//We could just do a simple check here to see if more than one item is in the list,
-//and if so fail. Make it so you build the menu first before allowing any other action. [DONE]
-
-//Right now, we just silently fail...
-       if (windowList.size() > 1)
-       {
-               WriteLog("GUI: Can't find menu--more than one item in windowList!\n");
-               return;
-       }
-
-       ((Menu *)(*windowList.begin()))->Add(*menuItem);
-}
-
-
-void GUI::Run(void)
-{
-       exitGUI = false;
-       showMouse = true;
-       SDL_Event event;
-       std::list<Element *>::iterator i;
-
-// Not sure what replaces this in SDL2...
-//     SDL_EnableKeyRepeat(150, 75);
-
-       // Also: Need to pick up backbuffer (for those windows that have them)
-       //       BEFORE drawing...
-
-       // Initial update... [Now handled correctly in the constructor]
-       // Uh, still needed here, though... Only makes sense that it should
-       for(i=windowList.begin(); i!=windowList.end(); i++)
-               (*i)->Draw();
-
-#ifndef USE_NEW_MAINBUFFERING
-       RenderScreenBuffer();
-#else
-       FlipMainScreen();
-#endif
-
-       // Main loop
-       while (!exitGUI)
-       {
-//             if (SDL_PollEvent(&event))
-               if (SDL_WaitEvent(&event))
-               {
-#ifdef DEBUG_MAIN_LOOP
-WriteLog("An event was found!");
-#endif
-                       if (event.type == SDL_USEREVENT)
-                       {
-#ifdef DEBUG_MAIN_LOOP
-WriteLog(" -- SDL_USEREVENT\n");
-#endif
-//Mebbe add another user event for screen refresh? Why not!
-                               if (event.user.code == WINDOW_CLOSE)
-                               {
-                                       for(i=windowList.begin(); i!=windowList.end(); i++)
-                                       {
-                                               if (*i == (Element *)event.user.data1)
-                                               {
-                                                       delete *i;
-                                                       windowList.erase(i);
-                                                       break;
-                                               }
-                                       }
-                               }
-                               else if (event.user.code == MENU_ITEM_CHOSEN)
-                               {
-                                       // Confused? Let me enlighten... What we're doing here is casting
-                                       // data1 as a pointer to a function which returns a Element pointer and
-                                       // which takes no parameters (the "(Element *(*)(void))" part), then
-                                       // derefencing it (the "*" in front of that) in order to call the
-                                       // function that it points to. Clear as mud? Yeah, I hate function
-                                       // pointers too, but what else are you gonna do?
-                                       Element * window = (*(Element *(*)(void))event.user.data1)();
-
-                                       if (window)
-                                               windowList.push_back(window);
-
-                                       while (SDL_PollEvent(&event));  // Flush the event queue...
-
-                                       event.type = SDL_MOUSEMOTION;
-                                       int mx, my;
-                                       SDL_GetMouseState(&mx, &my);
-                                       event.motion.x = mx, event.motion.y = my;
-                                   SDL_PushEvent(&event);                      // & update mouse position...!
-
-                                       oldMouse.x = mouse.x, oldMouse.y = mouse.y;
-                                       mouse.x = mx, mouse.y = my;             // This prevents "mouse flash"...
-                               }
-//There's a *small* problem with the following approach--if a window and a bunch of
-//child widgets send this message, we'll get a bunch of unnecessary refresh events...
-//This could be controlled by having the main window refresh itself intelligently...
-
-//What we could do instead is set a variable in Element and check it after the fact
-//to see whether or not a refresh is needed.
-//[This is what we do now.]
-
-//Dirty rectangle is also possible...
-                               else if (event.user.code == SCREEN_REFRESH_NEEDED)
-#ifndef USE_NEW_MAINBUFFERING
-                                       RenderScreenBuffer();
-#else
-                                       FlipMainScreen();
-#endif
-                       }
-//Not sure what to do here for SDL2...
-#if 0
-                       else if (event.type == SDL_ACTIVEEVENT)
-                       {
-//Need to do a screen refresh here...
-                               if (event.active.state == SDL_APPMOUSEFOCUS)
-                                       showMouse = (event.active.gain ? true : false);
-
-#ifndef USE_NEW_MAINBUFFERING
-                               RenderScreenBuffer();
-#else
-                               FlipMainScreen();
-#endif
-                       }
-#endif
-                       else if (event.type == SDL_KEYDOWN)
-                       {
-#ifdef DEBUG_MAIN_LOOP
-WriteLog(" -- SDL_KEYDOWN\n");
-#endif
-                               if (event.key.keysym.sym == SDLK_F1)
-                                       exitGUI = true;
-
-//Not sure that this is the right way to handle this...
-//Probably should only give this to the top level window...
-//                             for(i=windowList.begin(); i!=windowList.end(); i++)
-//                                     (*i)->HandleKey(event.key.keysym.sym);
-                               windowList.back()->HandleKey(event.key.keysym.scancode);
-                       }
-                       else if (event.type == SDL_MOUSEMOTION)
-                       {
-#ifdef DEBUG_MAIN_LOOP
-WriteLog(" -- SDL_MOUSEMOTION\n");
-#endif
-//This is for tracking a custom mouse cursor, which we're not doing--YET.
-                               oldMouse.x = mouse.x, oldMouse.y = mouse.y;
-                               mouse.x = event.motion.x, mouse.y = event.motion.y;
-
-//Not sure that this is the right way to handle this...
-//Right now, we should probably only do mouseover for the last item in the list...
-//And now we do!
-//Though, it seems to screw other things up. Maybe it IS better to pass it to all windows?
-//Or maybe to just the ones that aren't completely obscured?
-//Probably. Right now, a disk's close button that should be obscured by one sitting on
-//top of it gets redrawn. Not good. !!! FIX !!!
-                               for(i=windowList.begin(); i!=windowList.end(); i++)
-                                       (*i)->HandleMouseMove(mouse.x, mouse.y);
-//                             windowList.back()->HandleMouseMove(mouse.x, mouse.y);
-                       }
-                       else if (event.type == SDL_MOUSEBUTTONDOWN)
-                       {
-#ifdef DEBUG_MAIN_LOOP
-WriteLog(" -- SDL_MOUSEBUTTONDOWN\n");
-#endif
-//Not sure that this is the right way to handle this...
-// What we should do here is ensure that whatever has been clicked on gets moved to the
-// highest priority--in our current data schema that would be the end of the list... !!! FIX !!!
-//[DONE]
-
-/*
-
-We could do the following:
-
-- Go through list and find which window has been clicked on (if any). If more
-  than one is clicked on, take the one highest in the Z order (closer to the end
-  of the list).
-
-- If item is highest in Z order, pass click through to window and exit.
-
-- Otherwise, restore backing store on each window in reverse order.
-
-- Remove item clicked on from the list. Put removed item at the end of the list.
-
-- Go through list and pass click through to each window in the list. Also do a
-  blit to backing store and a Draw() for each window.
-
-Could also do a check (if not clicked on highest Z window) to see which windows
-it overlaps and just do restore/redraw for those that overlap. To wit:
-
-- Create new list containing only those windows that overlap the clicking on window.
-
-- Go through list and do a blit to backing store and a Draw() for each window.
-
-- Go through list and pass click through to each window in the list.
-
-*/
-
-#if 0
-#if 0
-                               for(i=windowList.begin(); i!=windowList.end(); i++)
-                                       (*i)->HandleMouseButton(event.button.x, event.button.y, true);
-#else
-// We use the 1st algorithm here, since it's simpler. If we need to, we can optimize
-// to the 2nd...
-
-                               // Walk backward through the list and see if a window was hit.
-                               // This will automagically return us the window with the highest Z.
-
-                               std::list<Element *>::reverse_iterator ri;
-                               std::list<Element *>::iterator hit;// = windowList.end();
-
-                               for(ri=windowList.rbegin(); ri!=windowList.rend(); ri++)
-                               {
-                                       if ((*ri)->Inside(event.button.x, event.button.y))
-                                       {
-                                               // Here's a bit of STL weirdness: Converting from a reverse
-                                               // iterator to a regular iterator requires backing the iterator
-                                               // up a position after grabbing it's base() OR going forward
-                                               // one position with the reverse iterator before grabbing base().
-                                               // Ugly, but it gets the job done...
-                                               hit = (++ri).base();
-                                               // Put it back where we found it, so the tests following this
-                                               // don't fail...
-                                               ri--;
-                                               break;
-                                       }
-                               }
-
-                               // If we hit the highest in the list, then pass the event through
-                               // to the window for handling. if we hit no windows, then pass the
-                               // event to all windows. Otherwise, we need to shuffle windows.
-
-//NOTE: We need to pass the click to all windows regardless of whether they're topmost or not...
-                               if (ri == windowList.rbegin())
-                               {
-                                       for(i=windowList.begin(); i!=windowList.end(); i++)
-                                               (*i)->HandleMouseButton(event.button.x, event.button.y, true);
-                               }
-                               else if (ri == windowList.rend())
-                               {
-                                       for(i=windowList.begin(); i!=windowList.end(); i++)
-                                               (*i)->HandleMouseButton(event.button.x, event.button.y, true);
-                               }
-                               else
-                               {
-// - Otherwise, restore backing store on each window in reverse order.
-                                       for(ri=windowList.rbegin(); ri!=windowList.rend(); ri++)
-                                               (*ri)->RestoreScreenFromBackstore();
-                                       // At this point, the screen has been restored...
-
-// - Remove item clicked on from the list. Put removed item at the end of the list.
-                                       windowList.push_back(*hit);
-                                       windowList.erase(hit);
-// - Go through list and pass click through to each window in the list. Also do a
-//  blit to backing store and a Draw() for each window.
-                                       for(i=windowList.begin(); i!= windowList.end(); i++)
-                                       {
-                                               // Grab bg into backstore
-                                               (*i)->SaveScreenToBackstore();
-                                               // Pass click
-                                               (*i)->HandleMouseButton(event.button.x, event.button.y, true);
-                                               // Draw?
-                                               (*i)->Draw();
-                                       }
-                               }
-#endif
-#endif
-/*
-A slightly different way to handle this would be to loop through all windows, compare
-all those above it to see if they obscure it; if so then subdivide it's update rectangle
-to eliminate drawing the parts that aren't shown. The beauty of this approach is that
-you don't have to care what order the windows are drawn in and you don't need to worry
-about the order of restoring the backing store.
-
-You *do* still need to determine the Z-order of the windows, in order to get the subdivisions
-correct, but that's not too terrible.
-
-Also, when doing a window drag, the coverage lists for all windows have to be regenerated.
-*/
-                               std::list<Element *>::reverse_iterator ri;
-                               bool movedWindow = false;
-
-                               for(ri=windowList.rbegin(); ri!=windowList.rend(); ri++)
-                               {
-                                       if ((*ri)->Inside(event.button.x, event.button.y))
-                                       {
-                                               // Remove item clicked on from the list & put removed item at the
-                                               // end of the list, thus putting the window at the top of the Z
-                                               // order. But IFF window is not already topmost!
-                                               if (ri != windowList.rbegin())
-                                               {
-                                                       windowList.push_back(*ri);
-                                                       // Here's a bit of STL weirdness: Converting from a reverse
-                                                       // iterator to a regular iterator requires backing the iterator
-                                                       // up a position after grabbing it's base() OR going forward
-                                                       // one position with the reverse iterator before grabbing base().
-                                                       // Ugly, but it get the job done...
-                                                       windowList.erase((++ri).base());
-                                                       movedWindow = true;
-                                               }
-
-                                               break;
-                                       }
-                               }
-
-//Small problem here: we should only pass the *hit* to the topmost window and pass
-//*misses* to everyone else... Otherwise, you can have overlapping draggable windows
-//and be able to drag both by clicking on a point that intersects both...
-//(though that may be an interesting way to handle things!)
-//The thing is that you want to do it on purpose (like with a special grouping widget)
-//instead of by accident. So, !!! FIX !!!
-                               // Pass the click on to all windows
-//                             for(i=windowList.begin(); i!=windowList.end(); i++)
-//                                     (*i)->HandleMouseButton(event.button.x, event.button.y, true);
-                               windowList.back()->HandleMouseButton(event.button.x, event.button.y, true);
-
-//                             // & bail if nothing changed...
-                               if (movedWindow)
-//                                     return;
-{
-                               // Check for overlap/build coverage lists [O((n^2)/2) algorithm!]
-//One way to optimize this would be to only reset coverage lists from the point in
-//the Z order where the previous window was.
-                               for(i=windowList.begin(); i!=windowList.end(); i++)
-                               {
-//One other little quirk: Probably need to clear the backing store as well!
-//Not sure...
-                                       (*i)->ResetCoverageList();
-
-                                       // This looks odd, but it's just a consequence of iterator weirdness.
-                                       // Otherwise we could just stick a j+1 in the for loop below. :-P
-                                       std::list<Element *>::iterator j = i;
-                                       j++;
-
-                                       for(; j!=windowList.end(); j++)
-                                               (*i)->AdjustCoverageList((*j)->GetExtents());
-
-//                                     (*i)->HandleMouseButton(event.button.x, event.button.y, true);
-                                       (*i)->Draw();
-                               }
-}
-                       }
-                       else if (event.type == SDL_MOUSEBUTTONUP)
-                       {
-#ifdef DEBUG_MAIN_LOOP
-WriteLog(" -- SDL_MOUSEBUTTONUP\n");
-#endif
-//Not sure that this is the right way to handle this...
-                               for(i=windowList.begin(); i!=windowList.end(); i++)
-                                       (*i)->HandleMouseButton(event.button.x, event.button.y, false);
-//I think we should only do topmost here...
-//Or should we???
-//                             windowList.back()->HandleMouseButton(event.button.x, event.button.y, false);
-                       }
-#ifdef DEBUG_MAIN_LOOP
-else
-       WriteLog(" -- Unknown event\n");
-#endif
-
-                       if (Element::ScreenNeedsRefreshing())
-                       {
-#ifndef USE_NEW_MAINBUFFERING
-#ifdef DEBUG_MAIN_LOOP
-WriteLog("Screen refresh called!\n");
-#endif
-                               RenderScreenBuffer();
-                               Element::ScreenWasRefreshed();
-#else
-                               FlipMainScreen();
-                               Element::ScreenWasRefreshed();
-#endif
-                       }
-               }
-//hm. Works, but slows things way down.
-//Now we use WaitEvents() instead. Yay!
-//SDL_Delay(10);
-       }
-
-// Not sure what to do for this in SDL 2...
-//     SDL_EnableKeyRepeat(0, 0);
-//     return false;
-}
-
-
-void GUI::Stop(void)
-{
-       exitGUI = true;
-}
-
-
+#include "video.h"
 
-//
-// NEW GUI STARTS HERE
-//
+// Icons, in GIMP "C" format
+#include "gfx/icon-selection.c"
+#include "gfx/disk-icon.c"
+#include "gfx/power-off-icon.c"
+#include "gfx/power-on-icon.c"
+#include "gfx/disk-swap-icon.c"
+#include "gfx/disk-door-open.c"
+#include "gfx/disk-door-closed.c"
+#include "gfx/save-state-icon.c"
+#include "gfx/load-state-icon.c"
+#include "gfx/config-icon.c"
 
 
 // Okay, this is ugly but works and I can't think of any better way to handle
@@ -501,19 +57,6 @@ struct Bitmap {
 };
 
 
-// Icons, in GIMP "C" format
-#include "gfx/icon-selection.c"
-#include "gfx/disk-icon.c"
-#include "gfx/power-off-icon.c"
-#include "gfx/power-on-icon.c"
-#include "gfx/disk-swap-icon.c"
-#include "gfx/disk-door-open.c"
-#include "gfx/disk-door-closed.c"
-#include "gfx/save-state-icon.c"
-#include "gfx/load-state-icon.c"
-#include "gfx/config-icon.c"
-
-
 const char numeralOne[(7 * 7) + 1] =
        "  @@   "
        " @@@   "
@@ -541,6 +84,14 @@ const char ejectIcon[(8 * 7) + 1] =
        "@@@@@@@@"
        "@@@@@@@@";
 
+const char newDiskIcon[(30 * 6) + 1] =
+       "@@   @@ @@@@@ @@   @@     @@  "
+       "@@@  @@ @@    @@   @@    @@@@ "
+       "@@@@ @@ @@@@  @@ @ @@   @@@@@@"
+       "@@ @@@@ @@    @@@@@@@     @@  "
+       "@@  @@@ @@    @@@@@@@  @@@@@  "
+       "@@   @@ @@@@@ @@   @@  @@@@   ";
+
 const char driveLight[(5 * 5) + 1] =
        " @@@ "
        "@@@@@"
@@ -549,15 +100,14 @@ const char driveLight[(5 * 5) + 1] =
        " @@@ ";
 
 
-enum { SBS_SHOWING, SBS_HIDING, SBS_SHOWN, SBS_HIDDEN };
-
+SDL_Texture * GUI::overlay = NULL;
+SDL_Rect GUI::olDst;
+int GUI::sidebarState = SBS_HIDDEN;
+int32_t GUI::dx = 0;
+int32_t GUI::iconSelected = -1;
+bool GUI::hasKeyboardFocus = false;
+bool GUI::powerOnState = true;
 
-SDL_Texture * GUI2::overlay = NULL;
-SDL_Rect GUI2::olDst;
-int GUI2::sidebarState = SBS_HIDDEN;
-int32_t GUI2::dx = 0;
-int32_t GUI2::iconSelected = -1;
-bool GUI2::hasKeyboardFocus = false;
 int32_t lastIconSelected = -1;
 SDL_Texture * iconSelection = NULL;
 SDL_Texture * diskIcon = NULL;
@@ -576,24 +126,35 @@ const char iconHelp[7][80] = { "Turn emulated Apple off/on",
        "Insert floppy image into drive #1", "Insert floppy image into drive #2",
        "Swap disks", "Save emulator state", "Load emulator state",
        "Configure Apple2" };
+bool disk1EjectHovered = false;
+bool disk2EjectHovered = false;
+bool disk1NewDiskHovered = false;
+bool disk2NewDiskHovered = false;
+
+SDL_Texture * GUI::charStamp = NULL;
+uint32_t GUI::stamp[FONT_WIDTH * FONT_HEIGHT];
 
 #define SIDEBAR_X_POS  (VIRTUAL_SCREEN_WIDTH - 80)
 
+//std::vector<void *> objList;
+
 
-GUI2::GUI2(void)
+GUI::GUI(void)
 {
 }
 
 
-GUI2::~GUI2(void)
+GUI::~GUI(void)
 {
 }
 
 
-void GUI2::Init(SDL_Renderer * renderer)
+void GUI::Init(SDL_Renderer * renderer)
 {
        overlay = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888,
                SDL_TEXTUREACCESS_TARGET, 128, 380);
+       charStamp = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888,
+               SDL_TEXTUREACCESS_TARGET, FONT_WIDTH, FONT_HEIGHT);
 
        if (!overlay)
        {
@@ -604,6 +165,9 @@ void GUI2::Init(SDL_Renderer * renderer)
        if (SDL_SetTextureBlendMode(overlay, SDL_BLENDMODE_BLEND) == -1)
                WriteLog("GUI: Could not set blend mode for overlay.\n");
 
+       if (SDL_SetTextureBlendMode(charStamp, SDL_BLENDMODE_BLEND) == -1)
+               WriteLog("GUI: Could not set blend mode for charStamp.\n");
+
        for(uint32_t i=0; i<128*380; i++)
                texturePointer[i] = 0xB0A000A0;
 
@@ -643,13 +207,12 @@ void GUI2::Init(SDL_Renderer * renderer)
        }
 
        DiskSelector::Init(renderer);
-       DiskSelector::showWindow = true;
-
+       Config::Init(renderer);
        WriteLog("GUI: Successfully initialized.\n");
 }
 
 
-SDL_Texture * GUI2::CreateTexture(SDL_Renderer * renderer, const void * source)
+SDL_Texture * GUI::CreateTexture(SDL_Renderer * renderer, const void * source)
 {
        Bitmap * bitmap = (Bitmap *)source;
        SDL_Texture * texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888,
@@ -663,18 +226,97 @@ SDL_Texture * GUI2::CreateTexture(SDL_Renderer * renderer, const void * source)
 }
 
 
-void GUI2::MouseDown(int32_t x, int32_t y, uint32_t buttons)
+void GUI::MouseDown(int32_t x, int32_t y, uint32_t buttons)
 {
+       DiskSelector::MouseDown(x, y, buttons);
+       Config::MouseDown(x, y, buttons);
+
+       if (sidebarState != SBS_SHOWN)
+               return;
+
+       switch (iconSelected)
+       {
+       // Power
+       case 0:
+               powerOnState = !powerOnState;
+               SetPowerState();
+
+               if (!powerOnState)
+                       SpawnMessage("*** POWER OFF ***");
+
+               break;
+       // Disk #1
+       case 1:
+               SpawnMessage("*** DISK #1 ***");
+
+               if (disk1EjectHovered && !floppyDrive[0].IsEmpty(0))
+               {
+                       floppyDrive[0].EjectImage(0);
+                       SpawnMessage("*** DISK #1 EJECTED ***");
+               }
+
+               if (!disk1EjectHovered && !disk1NewDiskHovered)
+               {
+                       // Load the disk selector
+                       Config::HideWindow();
+                       DiskSelector::ShowWindow(0);
+               }
+
+               break;
+       // Disk #2
+       case 2:
+               SpawnMessage("*** DISK #2 ***");
+
+               if (disk2EjectHovered && !floppyDrive[0].IsEmpty(1))
+               {
+                       floppyDrive[0].EjectImage(1);
+                       SpawnMessage("*** DISK #2 EJECTED ***");
+               }
+
+               if (!disk2EjectHovered && !disk2NewDiskHovered)
+               {
+                       // Load the disk selector
+                       Config::HideWindow();
+                       DiskSelector::ShowWindow(1);
+               }
+
+               break;
+       // Swap disks
+       case 3:
+               floppyDrive[0].SwapImages();
+               SpawnMessage("*** DISKS SWAPPED ***");
+               break;
+       // Save state
+       case 4:
+               SpawnMessage("*** SAVE STATE ***");
+               break;
+       // Load state
+       case 5:
+               SpawnMessage("*** LOAD STATE ***");
+               break;
+       // Configuration
+       case 6:
+               SpawnMessage("*** CONFIGURATION ***");
+               // Load the configuration window
+               DiskSelector::HideWindow();
+               Config::ShowWindow();
+               break;
+       }
 }
 
 
-void GUI2::MouseUp(int32_t x, int32_t y, uint32_t buttons)
+void GUI::MouseUp(int32_t x, int32_t y, uint32_t buttons)
 {
+       DiskSelector::MouseUp(x, y, buttons);
+       Config::MouseUp(x, y, buttons);
 }
 
 
-void GUI2::MouseMove(int32_t x, int32_t y, uint32_t buttons)
+void GUI::MouseMove(int32_t x, int32_t y, uint32_t buttons)
 {
+       DiskSelector::MouseMove(x, y, buttons);
+       Config::MouseMove(x, y, buttons);
+
        if (sidebarState != SBS_SHOWN)
        {
                iconSelected = -1;
@@ -713,17 +355,40 @@ void GUI2::MouseMove(int32_t x, int32_t y, uint32_t buttons)
                        else
                                iconSelected = (y - 4) / 54;
 
+                       // It's y+2 because the sidebar sits at (SIDEBAR_X_POS, 2)
+                       disk1EjectHovered = ((x >= (SIDEBAR_X_POS + 24 + 29))
+                               && (x < (SIDEBAR_X_POS + 24 + 29 + 8))
+                               && (y >= (63 + 31 + 2))
+                               && (y < (63 + 31 + 2 + 7)) ? true : false);
+
+                       disk2EjectHovered = ((x >= (SIDEBAR_X_POS + 24 + 29))
+                               && (x < (SIDEBAR_X_POS + 24 + 29 + 8))
+                               && (y >= (117 + 31 + 2))
+                               && (y < (117 + 31 + 2 + 7)) ? true : false);
+
+                       disk1NewDiskHovered = ((x >= (SIDEBAR_X_POS + 24 + 6))
+                               && (x < (SIDEBAR_X_POS + 24 + 6 + 30))
+                               && (y >= (63 + 31 + 2))
+                               && (y < (63 + 31 + 2 + 6)) ? true : false);
+
+                       disk2NewDiskHovered = ((x >= (SIDEBAR_X_POS + 24 + 6))
+                               && (x < (SIDEBAR_X_POS + 24 + 6 + 30))
+                               && (y >= (117 + 31 + 2))
+                               && (y < (117 + 31 + 2 + 6)) ? true : false);
+
                        if (iconSelected != lastIconSelected)
                        {
                                HandleIconSelection(sdlRenderer);
                                lastIconSelected = iconSelected;
-                               SpawnMessage("%s", iconHelp[iconSelected]);
+
+                               if ((iconSelected >= 0) && (iconSelected <= 6))
+                                       SpawnMessage("%s", iconHelp[iconSelected]);
 
                                // Show what's in the selected drive
                                if (iconSelected >= 1 && iconSelected <= 2)
                                {
-                                       if (!floppyDrive.IsEmpty(iconSelected - 1))
-                                               SpawnMessage("\"%s\"", floppyDrive.ImageName(iconSelected - 1));
+                                       if (!floppyDrive[0].IsEmpty(iconSelected - 1))
+                                               SpawnMessage("\"%s\"", floppyDrive[0].ImageName(iconSelected - 1));
                                }
                        }
                }
@@ -731,7 +396,13 @@ void GUI2::MouseMove(int32_t x, int32_t y, uint32_t buttons)
 }
 
 
-void GUI2::HandleIconSelection(SDL_Renderer * renderer)
+bool GUI::KeyDown(uint32_t key)
+{
+       return Config::KeyDown(key);
+}
+
+
+void GUI::HandleIconSelection(SDL_Renderer * renderer)
 {
        // Set up drive icons in their current states
        AssembleDriveIcon(renderer, 0);
@@ -761,7 +432,7 @@ void GUI2::HandleIconSelection(SDL_Renderer * renderer)
 }
 
 
-void GUI2::AssembleDriveIcon(SDL_Renderer * renderer, int driveNumber)
+void GUI::AssembleDriveIcon(SDL_Renderer * renderer, int driveNumber)
 {
        SDL_Texture * drive[2] = { disk1Icon, disk2Icon };
        const char * number[2] = { numeralOne, numeralTwo };
@@ -778,31 +449,53 @@ void GUI2::AssembleDriveIcon(SDL_Renderer * renderer, int driveNumber)
        // Drive door @ (16, 7)
        SDL_Rect dst;
        dst.w = 8, dst.h = 10, dst.x = 16, dst.y = 7;
-       SDL_RenderCopy(renderer, (floppyDrive.IsEmpty(driveNumber) ?
+       SDL_RenderCopy(renderer, (floppyDrive[0].IsEmpty(driveNumber) ?
                doorOpen : doorClosed), NULL, &dst);
 
        // Numeral @ (30, 20)
        DrawCharArray(renderer, number[driveNumber], 30, 20, 7, 7, 0xD0, 0xE0, 0xF0);
        DrawDriveLight(renderer, driveNumber);
        DrawEjectButton(renderer, driveNumber);
+       DrawNewDiskButton(renderer, driveNumber);
 
        // Set render target back to default
        SDL_SetRenderTarget(renderer, NULL);
 }
 
 
-void GUI2::DrawEjectButton(SDL_Renderer * renderer, int driveNumber)
+void GUI::DrawEjectButton(SDL_Renderer * renderer, int driveNumber)
 {
-       if (floppyDrive.IsEmpty(driveNumber))
+       if (floppyDrive[0].IsEmpty(driveNumber))
                return;
 
-       DrawCharArray(renderer, ejectIcon, 29, 31, 8, 7, 0x00, 0xAA, 0x00);
+       uint8_t r = 0x00, g = 0xAA, b = 0x00;
+
+       if ((driveNumber == 0 && disk1EjectHovered)
+               || (driveNumber == 1 && disk2EjectHovered))
+               r = 0x20, g = 0xFF, b = 0x20;
+
+       DrawCharArray(renderer, ejectIcon, 29, 31, 8, 7, r, g, b);
 }
 
 
-void GUI2::DrawDriveLight(SDL_Renderer * renderer, int driveNumber)
+void GUI::DrawNewDiskButton(SDL_Renderer * renderer, int driveNumber)
 {
-       int lightState = floppyDrive.DriveLightStatus(driveNumber);
+       if (!floppyDrive[0].IsEmpty(driveNumber))
+               return;
+
+       uint8_t r = 0x00, g = 0xAA, b = 0x00;
+
+       if ((driveNumber == 0 && disk1NewDiskHovered)
+               || (driveNumber == 1 && disk2NewDiskHovered))
+               r = 0x20, g = 0xFF, b = 0x20;
+
+       DrawCharArray(renderer, newDiskIcon, 6, 31, 30, 6, r, g, b);
+}
+
+
+void GUI::DrawDriveLight(SDL_Renderer * renderer, int driveNumber)
+{
+       int lightState = floppyDrive[0].DriveLightStatus(driveNumber);
        int r = 0x77, g = 0x00, b = 0x00;
 
        if (lightState == DLS_READ)
@@ -815,7 +508,7 @@ void GUI2::DrawDriveLight(SDL_Renderer * renderer, int driveNumber)
 }
 
 
-void GUI2::DrawCharArray(SDL_Renderer * renderer, const char * array, int x,
+void GUI::DrawCharArray(SDL_Renderer * renderer, const char * array, int x,
        int y, int w, int h, int r, int g, int b)
 {
        SDL_SetRenderDrawColor(renderer, r, g, b, 0xFF);
@@ -833,7 +526,86 @@ void GUI2::DrawCharArray(SDL_Renderer * renderer, const char * array, int x,
 }
 
 
-void GUI2::HandleGUIState(void)
+void GUI::DrawCharacter(SDL_Renderer * renderer, int x, int y, uint8_t c, bool invert/*= false*/)
+{
+       uint32_t inv = (invert ? 0x000000FF : 0x00000000);
+       uint32_t pixel = 0xFFFFC000;    // RRGGBBAA
+       uint8_t * ptr = (uint8_t *)&font10pt[(c - 0x20) * FONT_WIDTH * FONT_HEIGHT];
+       SDL_Rect dst;
+       dst.x = x * FONT_WIDTH, dst.y = y * FONT_HEIGHT, dst.w = FONT_WIDTH, dst.h = FONT_HEIGHT;
+
+       for(int i=0; i<FONT_WIDTH*FONT_HEIGHT; i++)
+               stamp[i] = (pixel | ptr[i]) ^ inv;
+
+       SDL_UpdateTexture(charStamp, NULL, stamp, FONT_WIDTH * sizeof(Uint32));
+       SDL_RenderCopy(renderer, charStamp, NULL, &dst);
+}
+
+
+//
+// N.B.: This draws a char at an abosulte X/Y position, not on a grid
+//
+void GUI::DrawCharacterVert(SDL_Renderer * renderer, int x, int y, uint8_t c, bool invert/*= false*/)
+{
+       uint32_t inv = (invert ? 0x000000FF : 0x00000000);
+       uint32_t pixel = 0xFFFFC000;    // RRGGBBAA
+       uint8_t * ptr = (uint8_t *)&font10pt[(c - 0x20) * FONT_WIDTH * FONT_HEIGHT];
+       SDL_Rect dst;
+       dst.x = x, dst.y = y, dst.w = FONT_WIDTH, dst.h = FONT_HEIGHT;
+       SDL_Point pt = { FONT_WIDTH - 1, FONT_HEIGHT - 1 };
+
+       for(int i=0; i<FONT_WIDTH*FONT_HEIGHT; i++)
+               stamp[i] = (pixel | ptr[i]) ^ inv;
+
+       SDL_SetTextureBlendMode(charStamp, SDL_BLENDMODE_NONE);
+       SDL_UpdateTexture(charStamp, NULL, stamp, FONT_WIDTH * sizeof(Uint32));
+       SDL_RenderCopyEx(renderer, charStamp, NULL, &dst, 270.0, &pt, SDL_FLIP_NONE);
+       SDL_SetTextureBlendMode(charStamp, SDL_BLENDMODE_BLEND);
+}
+
+
+void GUI::DrawString(SDL_Renderer * renderer, int x, int y, const char * s, bool invert/*= false*/)
+{
+       int len = strlen(s);
+
+       for(int i=0; i<len; i++)
+               DrawCharacter(renderer, x + i, y, s[i], invert);
+}
+
+
+//
+// N.B.: This draws a char at an abosulte X/Y position, not on a grid
+//
+void GUI::DrawStringVert(SDL_Renderer * renderer, int x, int y, const char * s, bool invert/*= false*/)
+{
+       int len = strlen(s);
+
+       for(int i=0; i<len; i++)
+               DrawCharacterVert(renderer, x, y - (FONT_WIDTH * i), s[i], invert);
+}
+
+
+void GUI::DrawBox(SDL_Renderer * renderer, int x, int y, int w, int h, int r/*= 0x00*/, int g/*= 0xAA*/, int b/*= 0x00*/)
+{
+       SDL_SetRenderDrawColor(renderer, r, g, b, 0xFF);
+
+       for(int i=0; i<w; i++)
+       {
+               SDL_RenderDrawPoint(renderer, x + i, y);
+               SDL_RenderDrawPoint(renderer, x + i, y + h - 1);
+       }
+
+       for(int i=0; i<h; i++)
+       {
+               SDL_RenderDrawPoint(renderer, x, y + i);
+               SDL_RenderDrawPoint(renderer, x + w - 1, y + i);
+       }
+
+       SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00);
+}
+
+
+void GUI::HandleGUIState(void)
 {
        olDst.x += dx;
 
@@ -852,13 +624,13 @@ void GUI2::HandleGUIState(void)
 }
 
 
-void GUI2::DrawSidebarIcons(SDL_Renderer * renderer)
+void GUI::DrawSidebarIcons(SDL_Renderer * renderer)
 {
        SDL_Texture * icons[7] = { powerOnIcon, disk1Icon, disk2Icon, diskSwapIcon,
                stateSaveIcon, stateLoadIcon, configIcon };
 
-       SDL_Rect dst;
-       dst.w = dst.h = 40, dst.x = 24, dst.y = 2 + 7;
+       icons[0] = (powerOnState ? powerOnIcon : powerOffIcon);
+       SDL_Rect dst = { 24, 2 + 7, 40, 40 };
 
        for(int i=0; i<7; i++)
        {
@@ -868,8 +640,9 @@ void GUI2::DrawSidebarIcons(SDL_Renderer * renderer)
 }
 
 
-void GUI2::Render(SDL_Renderer * renderer)
+void GUI::Render(SDL_Renderer * renderer)
 {
+       // Sanity check
        if (!overlay)
                return;
 
@@ -882,6 +655,7 @@ void GUI2::Render(SDL_Renderer * renderer)
 
        // Hmm.
        DiskSelector::Render(renderer);
+       Config::Render(renderer);
 }