robmcmullen-apple2/src/gui/gui.cpp
Shamus Hammons 01d41f5b9d Improvements to timing, disk selector; added Double LoRes.
Having taken a good look at "Understanding the Apple IIe", I've fixed
the timing of one frame now that I know how much time it's supposed to
take. Also added the ability to recurse into subdirectories of the disks
directory, and the ability to page through the disk list when there are
more than three columns of disks. Another thing I did not know about was
Double LoRes, that has been added as well; also, did not know that the
video bus appeared at memory mapped locations that weren't attached to
anything. This quirk is now properly emulated.
2017-06-21 22:57:57 -05:00

564 lines
12 KiB
C++

//
// gui.cpp
//
// Graphical User Interface support
// by James Hammons
// © 2014 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:
//
// - Memory leak on quitting with a window active [DONE]
// - Multiple window handling [DONE]
//
#include "gui.h"
#include "apple2.h"
#include "diskselector.h"
#include "log.h"
#include "video.h"
// 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 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;
#define SIDEBAR_X_POS (VIRTUAL_SCREEN_WIDTH - 80)
GUI::GUI(void)
{
}
GUI::~GUI(void)
{
}
void GUI::Init(SDL_Renderer * renderer)
{
overlay = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888,
SDL_TEXTUREACCESS_TARGET, 128, 380);
if (!overlay)
{
WriteLog("GUI: Could not create overlay!\n");
return;
}
if (SDL_SetTextureBlendMode(overlay, SDL_BLENDMODE_BLEND) == -1)
WriteLog("GUI: Could not set blend mode for overlay.\n");
for(uint32_t i=0; i<128*380; i++)
texturePointer[i] = 0xB0A000A0;
SDL_UpdateTexture(overlay, NULL, texturePointer, 128 * sizeof(Uint32));
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);
if (SDL_SetRenderTarget(renderer, overlay) < 0)
{
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);
}
DiskSelector::Init(renderer);
WriteLog("GUI: Successfully initialized.\n");
}
SDL_Texture * GUI::CreateTexture(SDL_Renderer * renderer, const void * source)
{
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;
}
void GUI::MouseDown(int32_t x, int32_t y, uint32_t buttons)
{
DiskSelector::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.IsEmpty(0))
{
floppyDrive.EjectImage(0);
SpawnMessage("*** DISK #1 EJECTED ***");
}
if (!disk1EjectHovered)
{
// Load the disk selector
DiskSelector::ShowWindow(0);
}
break;
// Disk #2
case 2:
SpawnMessage("*** DISK #2 ***");
if (disk2EjectHovered && !floppyDrive.IsEmpty(1))
{
floppyDrive.EjectImage(1);
SpawnMessage("*** DISK #2 EJECTED ***");
}
if (!disk2EjectHovered)
{
// Load the disk selector
DiskSelector::ShowWindow(1);
}
break;
// Swap disks
case 3:
floppyDrive.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 ***");
break;
}
}
void GUI::MouseUp(int32_t x, int32_t y, uint32_t buttons)
{
DiskSelector::MouseUp(x, y, buttons);
}
void GUI::MouseMove(int32_t x, int32_t y, uint32_t buttons)
{
DiskSelector::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
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);
if (iconSelected != lastIconSelected)
{
HandleIconSelection(sdlRenderer);
lastIconSelected = 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));
}
}
}
}
}
void GUI::HandleIconSelection(SDL_Renderer * renderer)
{
// Set up drive icons in their current states
AssembleDriveIcon(renderer, 0);
AssembleDriveIcon(renderer, 1);
// Reload the background...
SDL_UpdateTexture(overlay, NULL, texturePointer, 128 * sizeof(Uint32));
if (SDL_SetRenderTarget(renderer, overlay) < 0)
{
WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
return;
}
// 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);
}
DrawSidebarIcons(renderer);
// Set render target back to default
SDL_SetRenderTarget(renderer, NULL);
}
void GUI::AssembleDriveIcon(SDL_Renderer * renderer, int driveNumber)
{
SDL_Texture * drive[2] = { disk1Icon, disk2Icon };
const char * number[2] = { numeralOne, numeralTwo };
if (SDL_SetRenderTarget(renderer, drive[driveNumber]) < 0)
{
WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
return;
}
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, diskIcon, NULL, NULL);
// 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) ?
doorOpen : doorClosed), NULL, &dst);
// Numeral @ (30, 20)
DrawCharArray(renderer, number[driveNumber], 30, 20, 7, 7, 0xD0, 0xE0, 0xF0);
DrawDriveLight(renderer, driveNumber);
DrawEjectButton(renderer, driveNumber);
// Set render target back to default
SDL_SetRenderTarget(renderer, NULL);
}
void GUI::DrawEjectButton(SDL_Renderer * renderer, int driveNumber)
{
if (floppyDrive.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);
}
void GUI::DrawDriveLight(SDL_Renderer * renderer, int driveNumber)
{
int lightState = floppyDrive.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_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00);
}
void GUI::HandleGUIState(void)
{
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)
{
if (!overlay)
return;
HandleGUIState();
if (sidebarState != SBS_HIDDEN)
HandleIconSelection(renderer);
SDL_RenderCopy(renderer, overlay, NULL, &olDst);
// Hmm.
DiskSelector::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
*/