//
-// GUI.CPP
+// gui.cpp
//
// Graphical User Interface support
-// by James L. Hammons
+// by James Hammons
+// © 2014-2019 Underground Software
//
-// JLH = James L. Hammons <jlhamm@acm.org>
+// 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 <vector>
+#include "apple2.h"
+#include "config.h"
+#include "diskselector.h"
+#include "elements.h"
+#include "floppydrive.h"
+#include "font10pt.h"
+#include "log.h"
#include "video.h"
-// Debug support
-
-//#define DEBUG_MAIN_LOOP
-
-//#ifdef DEBUG_MAIN_LOOP
-#include "log.h"
-//#endif
+// 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
+// this. So what we do when we pass the GIMP bitmaps into a function is pass
+// them as a (void *) and then cast them as type (Bitmap *) in order to use
+// them. Yes, it's ugly. Come up with something better!
+
+struct Bitmap {
+ unsigned int width;
+ unsigned int height;
+ unsigned int bytesPerPixel; // 3:RGB, 4:RGBA
+ unsigned char pixelData[];
+};
+
+
+const char numeralOne[(7 * 7) + 1] =
+ " @@ "
+ " @@@ "
+ "@@@@ "
+ " @@ "
+ " @@ "
+ " @@ "
+ "@@@@@@ ";
+
+const char numeralTwo[(7 * 7) + 1] =
+ " @@@@@ "
+ "@@ @@"
+ " @@@"
+ " @@@@ "
+ " @@@ "
+ "@@ "
+ "@@@@@@@";
+
+const char ejectIcon[(8 * 7) + 1] =
+ " @@ "
+ " @@@@ "
+ " @@@@@@ "
+ "@@@@@@@@"
+ " "
+ "@@@@@@@@"
+ "@@@@@@@@";
+
+const char newDiskIcon[(30 * 6) + 1] =
+ "@@ @@ @@@@@ @@ @@ @@ "
+ "@@@ @@ @@ @@ @@ @@@@ "
+ "@@@@ @@ @@@@ @@ @ @@ @@@@@@"
+ "@@ @@@@ @@ @@@@@@@ @@ "
+ "@@ @@@ @@ @@@@@@@ @@@@@ "
+ "@@ @@ @@@@@ @@ @@ @@@@ ";
+
+const char driveLight[(5 * 5) + 1] =
+ " @@@ "
+ "@@@@@"
+ "@@@@@"
+ "@@@@@"
+ " @@@ ";
+
+
+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;
+
+int32_t lastIconSelected = -1;
+SDL_Texture * iconSelection = NULL;
+SDL_Texture * diskIcon = NULL;
+SDL_Texture * disk1Icon = NULL;
+SDL_Texture * disk2Icon = NULL;
+SDL_Texture * powerOnIcon = NULL;
+SDL_Texture * powerOffIcon = NULL;
+SDL_Texture * diskSwapIcon = NULL;
+SDL_Texture * stateSaveIcon = NULL;
+SDL_Texture * stateLoadIcon = NULL;
+SDL_Texture * configIcon = NULL;
+SDL_Texture * doorOpen = NULL;
+SDL_Texture * doorClosed = NULL;
+uint32_t texturePointer[128 * 380];
+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;
+
+
+GUI::GUI(void)
+{
+}
-GUI::GUI(SDL_Surface * mainSurface): menuItem(new MenuItems())
+GUI::~GUI(void)
{
- windowList.push_back(new Menu());
- Element::SetScreen(mainSurface);
}
-GUI::~GUI()
+
+void GUI::Init(SDL_Renderer * renderer)
{
- // Clean up menuItem, if any
+ 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 (menuItem)
- delete menuItem;
+ if (!overlay)
+ {
+ WriteLog("GUI: Could not create overlay!\n");
+ return;
+ }
- // Clean up the rest
+ if (SDL_SetTextureBlendMode(overlay, SDL_BLENDMODE_BLEND) == -1)
+ WriteLog("GUI: Could not set blend mode for overlay.\n");
- for(std::list<Element *>::iterator i=windowList.begin(); i!=windowList.end(); i++)
- if (*i)
- delete *i;
-}
+ if (SDL_SetTextureBlendMode(charStamp, SDL_BLENDMODE_BLEND) == -1)
+ WriteLog("GUI: Could not set blend mode for charStamp.\n");
-void GUI::AddMenuTitle(const char * title)
-{
- menuItem->title = title;
- menuItem->item.clear();
-}
+ for(uint32_t i=0; i<128*380; i++)
+ texturePointer[i] = 0xB0A000A0;
-void GUI::AddMenuItem(const char * item, Element * (* a)(void)/*= NULL*/, SDLKey k/*= SDLK_UNKNOWN*/)
-{
- menuItem->item.push_back(NameAction(item, a, k));
-}
+ SDL_UpdateTexture(overlay, NULL, texturePointer, 128 * sizeof(Uint32));
-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]
+ olDst.x = VIRTUAL_SCREEN_WIDTH;
+ olDst.y = 2;
+ olDst.w = 128;
+ olDst.h = 380;
+
+ iconSelection = CreateTexture(renderer, &icon_selection);
+ diskIcon = CreateTexture(renderer, &disk_icon);
+ doorOpen = CreateTexture(renderer, &door_open);
+ doorClosed = CreateTexture(renderer, &door_closed);
+ disk1Icon = CreateTexture(renderer, &disk_icon);
+ disk2Icon = CreateTexture(renderer, &disk_icon);
+ powerOffIcon = CreateTexture(renderer, &power_off);
+ powerOnIcon = CreateTexture(renderer, &power_on);
+ diskSwapIcon = CreateTexture(renderer, &disk_swap);
+ stateSaveIcon = CreateTexture(renderer, &save_state);
+ stateLoadIcon = CreateTexture(renderer, &load_state);
+ configIcon = CreateTexture(renderer, &config);
+
+ // Set up drive icons in their current states
+// AssembleDriveIcon(renderer, 0);
+// AssembleDriveIcon(renderer, 1);
-//Right now, we just silently fail...
- if (windowList.size() > 1)
+ if (SDL_SetRenderTarget(renderer, overlay) < 0)
{
- WriteLog("GUI: Can't find menu--more than one item in windowList!\n");
- return;
+ WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
+ }
+ else
+ {
+ DrawSidebarIcons(renderer);
+ // Set render target back to default
+ SDL_SetRenderTarget(renderer, NULL);
}
- ((Menu *)(*windowList.begin()))->Add(*menuItem);
+ DiskSelector::Init(renderer);
+ Config::Init(renderer);
+ WriteLog("GUI: Successfully initialized.\n");
}
-void GUI::Run(void)
+
+SDL_Texture * GUI::CreateTexture(SDL_Renderer * renderer, const void * source)
{
- exitGUI = false;
- showMouse = true;
- SDL_Event event;
- std::list<Element *>::iterator i;
+ Bitmap * bitmap = (Bitmap *)source;
+ SDL_Texture * texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888,
+// SDL_TEXTUREACCESS_STATIC, bitmap->width, bitmap->height);
+ SDL_TEXTUREACCESS_TARGET, bitmap->width, bitmap->height);
+ SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
+ SDL_UpdateTexture(texture, NULL, (Uint32 *)bitmap->pixelData,
+ bitmap->width * sizeof(Uint32));
+
+ return texture;
+}
- SDL_EnableKeyRepeat(150, 75);
- // Initial update... [Now handled correctly in the constructor]
- for(i=windowList.begin(); i!=windowList.end(); i++)
- (*i)->Draw();
+void GUI::MouseDown(int32_t x, int32_t y, uint32_t buttons)
+{
+ DiskSelector::MouseDown(x, y, buttons);
+ Config::MouseDown(x, y, buttons);
- RenderScreenBuffer();
+ if (sidebarState != SBS_SHOWN)
+ return;
- // Main loop
- while (!exitGUI)
+ switch (iconSelected)
{
- if (SDL_PollEvent(&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...
+ // Power
+ case 0:
+ powerOnState = !powerOnState;
+ SetPowerState();
+
+ if (!powerOnState)
+ SpawnMessage("*** POWER OFF ***");
+
+ break;
+ // Disk #1
+ case 1:
+ SpawnMessage("*** DISK #1 ***");
-//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.]
+#if 0
+ if (disk1EjectHovered && !floppyDrive[0].IsEmpty(0))
+ {
+ floppyDrive[0].EjectImage(0);
+ SpawnMessage("*** DISK #1 EJECTED ***");
+ }
-//Dirty rectangle is also possible...
- else if (event.user.code == SCREEN_REFRESH_NEEDED)
- RenderScreenBuffer();
+ if (!disk1EjectHovered && !disk1NewDiskHovered)
+ {
+ // Load the disk selector
+ Config::HideWindow();
+ DiskSelector::ShowWindow(0);
+ }
+#else
+ if (disk1EjectHovered)
+ {
+ if (!floppyDrive[0].IsEmpty(0))
+ {
+ floppyDrive[0].EjectImage(0);
+ SpawnMessage("*** DISK #1 EJECTED ***");
}
- else if (event.type == SDL_ACTIVEEVENT)
+ }
+ else
+ {
+ if (disk1NewDiskHovered)
{
-//Need to do a screen refresh here...
- if (event.active.state == SDL_APPMOUSEFOCUS)
- showMouse = (event.active.gain ? true : false);
+ if (!floppyDrive[0].IsEmpty(0))
+ floppyDrive[0].EjectImage(0);
- RenderScreenBuffer();
+ floppyDrive[0].CreateBlankImage(0);
}
- else if (event.type == SDL_KEYDOWN)
+ else
{
-#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.sym);
+ // Load the disk selector
+ Config::HideWindow();
+ DiskSelector::ShowWindow(0);
}
- 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.
- for(i=windowList.begin(); i!=windowList.end(); i++)
- (*i)->HandleMouseMove(mouse.x, mouse.y);
-// windowList.back()->HandleMouseMove(mouse.x, mouse.y);
+
+ 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 GUI::MouseUp(int32_t x, int32_t y, uint32_t buttons)
+{
+ DiskSelector::MouseUp(x, y, buttons);
+ Config::MouseUp(x, y, 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;
+
+ if (x > SIDEBAR_X_POS)
+ {
+//printf("GUI: sidebar showing (x = %i)...\n", x);
+ sidebarState = SBS_SHOWING;
+ dx = -8;
+ }
+ else
+ {
+//printf("GUI: sidebar hiding[1] (x = %i)...\n", x);
+ sidebarState = SBS_HIDING;
+ dx = 8;
+ }
+ }
+ else
+ {
+ if (x < SIDEBAR_X_POS)
+ {
+ iconSelected = lastIconSelected = -1;
+ HandleIconSelection(sdlRenderer);
+//printf("GUI: sidebar hiding[2] (x = %i)...\n", x);
+ sidebarState = SBS_HIDING;
+ dx = 8;
+ }
+ // We're in the right zone, and the sidebar is shown, so let's select
+ // something!
+ else
+ {
+ if (y < 4 || y > 383)
+ {
+ iconSelected = -1;
}
- else if (event.type == SDL_MOUSEBUTTONDOWN)
+ 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)
{
-#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]
+ HandleIconSelection(sdlRenderer);
+ lastIconSelected = iconSelected;
-/*
+ if ((iconSelected >= 0) && (iconSelected <= 6))
+ SpawnMessage("%s", iconHelp[iconSelected]);
-We could do the following:
+ // Show what's in the selected drive
+ if (iconSelected >= 1 && iconSelected <= 2)
+ {
+ if (!floppyDrive[0].IsEmpty(iconSelected - 1))
+ SpawnMessage("\"%s\"", floppyDrive[0].ImageName(iconSelected - 1));
+ }
+ }
+ }
+ }
+}
-- 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.
+bool GUI::KeyDown(uint32_t key)
+{
+ return Config::KeyDown(key);
+}
-- 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.
+void GUI::HandleIconSelection(SDL_Renderer * renderer)
+{
+ // Set up drive icons in their current states
+ AssembleDriveIcon(renderer, 0);
+ AssembleDriveIcon(renderer, 1);
-- 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.
+ // Reload the background...
+ SDL_UpdateTexture(overlay, NULL, texturePointer, 128 * sizeof(Uint32));
-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:
+ if (SDL_SetRenderTarget(renderer, overlay) < 0)
+ {
+ WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
+ return;
+ }
-- Create new list containing only those windows that overlap the clicking on window.
+ // Draw the icon selector, if an icon is selected
+ if (iconSelected >= 0)
+ {
+ SDL_Rect dst;// = { 54, 54, 24 - 7, 2 };
+ dst.w = dst.h = 54, dst.x = 24 - 7, dst.y = 2 + (iconSelected * 54);
+ SDL_RenderCopy(renderer, iconSelection, NULL, &dst);
+ }
-- Go through list and do a blit to backing store and a Draw() for each window.
+ DrawSidebarIcons(renderer);
-- Go through list and pass click through to each window in the list.
+ // Set render target back to default
+ SDL_SetRenderTarget(renderer, NULL);
+}
-*/
-#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...
+void GUI::AssembleDriveIcon(SDL_Renderer * renderer, int driveNumber)
+{
+ SDL_Texture * drive[2] = { disk1Icon, disk2Icon };
+ const char * number[2] = { numeralOne, numeralTwo };
- // Walk backward through the list and see if a window was hit.
- // This will automagically return us the window with the highest Z.
+ if (SDL_SetRenderTarget(renderer, drive[driveNumber]) < 0)
+ {
+ WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
+ return;
+ }
- std::list<Element *>::reverse_iterator ri;
- std::list<Element *>::iterator hit;// = windowList.end();
+ SDL_RenderClear(renderer);
+ SDL_RenderCopy(renderer, diskIcon, NULL, NULL);
- 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;
- }
- }
+ // Drive door @ (16, 7)
+ SDL_Rect dst;
+ dst.w = 8, dst.h = 10, dst.x = 16, dst.y = 7;
+ SDL_RenderCopy(renderer, (floppyDrive[0].IsEmpty(driveNumber) ?
+ doorOpen : doorClosed), NULL, &dst);
- // 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.
+ // Numeral @ (30, 20)
+ DrawCharArray(renderer, number[driveNumber], 30, 20, 7, 7, 0xD0, 0xE0, 0xF0);
+ DrawDriveLight(renderer, driveNumber);
+ DrawEjectButton(renderer, driveNumber);
+ DrawNewDiskButton(renderer, driveNumber);
-//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.
+ // Set render target back to default
+ SDL_SetRenderTarget(renderer, NULL);
+}
-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;
+void GUI::DrawEjectButton(SDL_Renderer * renderer, int driveNumber)
+{
+ if (floppyDrive[0].IsEmpty(driveNumber))
+ return;
+
+ 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);
+}
- 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!)
- // Pass the click on to all windows
- for(i=windowList.begin(); i!=windowList.end(); i++)
- (*i)->HandleMouseButton(event.button.x, event.button.y, true);
-
-// // & bail if nothing changed...
- if (movedWindow)
-// return;
+void GUI::DrawNewDiskButton(SDL_Renderer * renderer, int driveNumber)
{
- // 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++)
- {
- (*i)->ResetCoverageList();
+ if (!floppyDrive[0].IsEmpty(driveNumber))
+ return;
- // 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++;
+ uint8_t r = 0x00, g = 0xAA, b = 0x00;
- for(; j!=windowList.end(); j++)
- (*i)->AdjustCoverageList((*j)->GetExtents());
+ if ((driveNumber == 0 && disk1NewDiskHovered)
+ || (driveNumber == 1 && disk2NewDiskHovered))
+ r = 0x20, g = 0xFF, b = 0x20;
-// (*i)->HandleMouseButton(event.button.x, event.button.y, true);
- (*i)->Draw();
- }
+ DrawCharArray(renderer, newDiskIcon, 6, 31, 30, 6, r, g, b);
}
- }
- 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())
- {
-#ifdef DEBUG_MAIN_LOOP
-WriteLog("Screen refresh called!\n");
-#endif
- RenderScreenBuffer();
- Element::ScreenWasRefreshed();
- }
+
+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)
+ r = 0x20, g = 0xFF, b = 0x20;
+ else if (lightState == DLS_WRITE)
+ r = 0xFF, g = 0x30, b = 0x30;
+
+ // Drive light @ (8, 21)
+ DrawCharArray(renderer, driveLight, 8, 21, 5, 5, r, g, b);
+}
+
+
+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);
+
+ for(int j=0; j<h; j++)
+ {
+ for(int i=0; i<w; i++)
+ {
+ if (array[(j * w) + i] != ' ')
+ SDL_RenderDrawPoint(renderer, x + i, y + j);
}
}
- SDL_EnableKeyRepeat(0, 0);
-// return false;
+ SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00);
+}
+
+
+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 absolute 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::Stop(void)
+
+void GUI::HandleGUIState(void)
{
- exitGUI = true;
+ olDst.x += dx;
+
+ if (olDst.x < SIDEBAR_X_POS && sidebarState == SBS_SHOWING)
+ {
+ olDst.x = SIDEBAR_X_POS;
+ sidebarState = SBS_SHOWN;
+ dx = 0;
+ }
+ else if (olDst.x > VIRTUAL_SCREEN_WIDTH && sidebarState == SBS_HIDING)
+ {
+ olDst.x = VIRTUAL_SCREEN_WIDTH;
+ sidebarState = SBS_HIDDEN;
+ dx = 0;
+ }
}
+
+
+void GUI::DrawSidebarIcons(SDL_Renderer * renderer)
+{
+ SDL_Texture * icons[7] = { powerOnIcon, disk1Icon, disk2Icon, diskSwapIcon,
+ stateSaveIcon, stateLoadIcon, configIcon };
+
+ icons[0] = (powerOnState ? powerOnIcon : powerOffIcon);
+ SDL_Rect dst = { 24, 2 + 7, 40, 40 };
+
+ for(int i=0; i<7; i++)
+ {
+ SDL_RenderCopy(renderer, icons[i], NULL, &dst);
+ dst.y += 54;
+ }
+}
+
+
+void GUI::Render(SDL_Renderer * renderer)
+{
+ // Sanity check
+ if (!overlay)
+ return;
+
+ HandleGUIState();
+
+ if (sidebarState != SBS_HIDDEN)
+ HandleIconSelection(renderer);
+
+ SDL_RenderCopy(renderer, overlay, NULL, &olDst);
+
+ // Hmm.
+ DiskSelector::Render(renderer);
+ Config::Render(renderer);
+}
+
+
+/*
+GUI Considerations:
+
+screen is 560 x 384
+
+cut into 7 pieces give ~54 pix per piece
+So, let's try 40x40 icons, and see if that's good enough...
+Selection is 54x54.
+
+drive proportions: 1.62 : 1
+
+Icon order:
+
++-----+
+| |
+|Power|
+| |
++-----+
+
++-----+
+| |
+|Disk1|
+| ^| <-- eject button
++-----+
+
++-----+
+| |
+|Disk2|
+| ^|
++-----+
+
++-----+
+| |
+|Swap |
+| |
++-----+
+
++-----+
+| |
+|Confg|
+| |
++-----+
+
+maybe state save/load
+
+*/
+