4 // Graphical User Interface support
6 // © 2014 Underground Software
8 // JLH = James Hammons <jlhamm@acm.org>
11 // --- ---------- -----------------------------------------------------------
12 // JLH 02/03/2006 Created this file
13 // JLH 03/13/2006 Added functions to allow shutting down GUI externally
14 // JLH 03/22/2006 Finalized basic multiple window support
15 // JLH 03/03/2014 Refactored GUI to use SDL 2, more modern approach as well
19 // - Memory leak on quitting with a window active [DONE]
20 // - Multiple window handling [DONE]
25 #include "diskselector.h"
29 // Icons, in GIMP "C" format
30 #include "gfx/icon-selection.c"
31 #include "gfx/disk-icon.c"
32 #include "gfx/power-off-icon.c"
33 #include "gfx/power-on-icon.c"
34 #include "gfx/disk-swap-icon.c"
35 #include "gfx/disk-door-open.c"
36 #include "gfx/disk-door-closed.c"
37 #include "gfx/save-state-icon.c"
38 #include "gfx/load-state-icon.c"
39 #include "gfx/config-icon.c"
42 // Okay, this is ugly but works and I can't think of any better way to handle
43 // this. So what we do when we pass the GIMP bitmaps into a function is pass
44 // them as a (void *) and then cast them as type (Bitmap *) in order to use
45 // them. Yes, it's ugly. Come up with something better!
50 unsigned int bytesPerPixel; // 3:RGB, 4:RGBA
51 unsigned char pixelData[];
55 const char numeralOne[(7 * 7) + 1] =
64 const char numeralTwo[(7 * 7) + 1] =
73 const char ejectIcon[(8 * 7) + 1] =
82 const char driveLight[(5 * 5) + 1] =
90 enum { SBS_SHOWING, SBS_HIDING, SBS_SHOWN, SBS_HIDDEN };
93 SDL_Texture * GUI::overlay = NULL;
95 int GUI::sidebarState = SBS_HIDDEN;
97 int32_t GUI::iconSelected = -1;
98 bool GUI::hasKeyboardFocus = false;
99 bool GUI::powerOnState = true;
101 int32_t lastIconSelected = -1;
102 SDL_Texture * iconSelection = NULL;
103 SDL_Texture * diskIcon = NULL;
104 SDL_Texture * disk1Icon = NULL;
105 SDL_Texture * disk2Icon = NULL;
106 SDL_Texture * powerOnIcon = NULL;
107 SDL_Texture * powerOffIcon = NULL;
108 SDL_Texture * diskSwapIcon = NULL;
109 SDL_Texture * stateSaveIcon = NULL;
110 SDL_Texture * stateLoadIcon = NULL;
111 SDL_Texture * configIcon = NULL;
112 SDL_Texture * doorOpen = NULL;
113 SDL_Texture * doorClosed = NULL;
114 uint32_t texturePointer[128 * 380];
115 const char iconHelp[7][80] = { "Turn emulated Apple off/on",
116 "Insert floppy image into drive #1", "Insert floppy image into drive #2",
117 "Swap disks", "Save emulator state", "Load emulator state",
118 "Configure Apple2" };
119 bool disk1EjectHovered = false;
120 bool disk2EjectHovered = false;
123 #define SIDEBAR_X_POS (VIRTUAL_SCREEN_WIDTH - 80)
136 void GUI::Init(SDL_Renderer * renderer)
138 overlay = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888,
139 SDL_TEXTUREACCESS_TARGET, 128, 380);
143 WriteLog("GUI: Could not create overlay!\n");
147 if (SDL_SetTextureBlendMode(overlay, SDL_BLENDMODE_BLEND) == -1)
148 WriteLog("GUI: Could not set blend mode for overlay.\n");
150 for(uint32_t i=0; i<128*380; i++)
151 texturePointer[i] = 0xB0A000A0;
153 SDL_UpdateTexture(overlay, NULL, texturePointer, 128 * sizeof(Uint32));
155 olDst.x = VIRTUAL_SCREEN_WIDTH;
160 iconSelection = CreateTexture(renderer, &icon_selection);
161 diskIcon = CreateTexture(renderer, &disk_icon);
162 doorOpen = CreateTexture(renderer, &door_open);
163 doorClosed = CreateTexture(renderer, &door_closed);
164 disk1Icon = CreateTexture(renderer, &disk_icon);
165 disk2Icon = CreateTexture(renderer, &disk_icon);
166 powerOffIcon = CreateTexture(renderer, &power_off);
167 powerOnIcon = CreateTexture(renderer, &power_on);
168 diskSwapIcon = CreateTexture(renderer, &disk_swap);
169 stateSaveIcon = CreateTexture(renderer, &save_state);
170 stateLoadIcon = CreateTexture(renderer, &load_state);
171 configIcon = CreateTexture(renderer, &config);
173 // Set up drive icons in their current states
174 // AssembleDriveIcon(renderer, 0);
175 // AssembleDriveIcon(renderer, 1);
177 if (SDL_SetRenderTarget(renderer, overlay) < 0)
179 WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
183 DrawSidebarIcons(renderer);
184 // Set render target back to default
185 SDL_SetRenderTarget(renderer, NULL);
188 DiskSelector::Init(renderer);
189 // DiskSelector::showWindow = true;
191 WriteLog("GUI: Successfully initialized.\n");
195 SDL_Texture * GUI::CreateTexture(SDL_Renderer * renderer, const void * source)
197 Bitmap * bitmap = (Bitmap *)source;
198 SDL_Texture * texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888,
199 // SDL_TEXTUREACCESS_STATIC, bitmap->width, bitmap->height);
200 SDL_TEXTUREACCESS_TARGET, bitmap->width, bitmap->height);
201 SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
202 SDL_UpdateTexture(texture, NULL, (Uint32 *)bitmap->pixelData,
203 bitmap->width * sizeof(Uint32));
209 void GUI::MouseDown(int32_t x, int32_t y, uint32_t buttons)
211 DiskSelector::MouseDown(x, y, buttons);
213 if (sidebarState != SBS_SHOWN)
216 switch (iconSelected)
220 powerOnState = !powerOnState;
224 SpawnMessage("*** POWER OFF ***");
229 SpawnMessage("*** DISK #1 ***");
231 if (disk1EjectHovered && !floppyDrive.IsEmpty(0))
233 floppyDrive.EjectImage(0);
234 SpawnMessage("*** DISK #1 EJECTED ***");
237 if (!disk1EjectHovered)
239 // Load the disk selector
240 DiskSelector::ShowWindow(0);
246 SpawnMessage("*** DISK #2 ***");
248 if (disk2EjectHovered && !floppyDrive.IsEmpty(1))
250 floppyDrive.EjectImage(1);
251 SpawnMessage("*** DISK #2 EJECTED ***");
254 if (!disk2EjectHovered)
256 // Load the disk selector
257 DiskSelector::ShowWindow(1);
263 floppyDrive.SwapImages();
264 SpawnMessage("*** DISKS SWAPPED ***");
268 SpawnMessage("*** SAVE STATE ***");
272 SpawnMessage("*** LOAD STATE ***");
276 SpawnMessage("*** CONFIGURATION ***");
282 void GUI::MouseUp(int32_t x, int32_t y, uint32_t buttons)
284 DiskSelector::MouseUp(x, y, buttons);
288 void GUI::MouseMove(int32_t x, int32_t y, uint32_t buttons)
290 DiskSelector::MouseMove(x, y, buttons);
292 if (sidebarState != SBS_SHOWN)
296 if (x > SIDEBAR_X_POS)
298 //printf("GUI: sidebar showing (x = %i)...\n", x);
299 sidebarState = SBS_SHOWING;
304 //printf("GUI: sidebar hiding[1] (x = %i)...\n", x);
305 sidebarState = SBS_HIDING;
311 if (x < SIDEBAR_X_POS)
313 iconSelected = lastIconSelected = -1;
314 HandleIconSelection(sdlRenderer);
315 //printf("GUI: sidebar hiding[2] (x = %i)...\n", x);
316 sidebarState = SBS_HIDING;
319 // We're in the right zone, and the sidebar is shown, so let's select
323 if (y < 4 || y > 383)
328 iconSelected = (y - 4) / 54;
330 // It's y+2 because the sidebar sits at (SIDEBAR_X_POS, 2)
331 disk1EjectHovered = ((x >= (SIDEBAR_X_POS + 24 + 29))
332 && (x < (SIDEBAR_X_POS + 24 + 29 + 8))
333 && (y >= (63 + 31 + 2))
334 && (y < (63 + 31 + 2 + 7)) ? true : false);
336 disk2EjectHovered = ((x >= (SIDEBAR_X_POS + 24 + 29))
337 && (x < (SIDEBAR_X_POS + 24 + 29 + 8))
338 && (y >= (117 + 31 + 2))
339 && (y < (117 + 31 + 2 + 7)) ? true : false);
341 if (iconSelected != lastIconSelected)
343 HandleIconSelection(sdlRenderer);
344 lastIconSelected = iconSelected;
346 if ((iconSelected >= 0) && (iconSelected <= 6))
347 SpawnMessage("%s", iconHelp[iconSelected]);
349 // Show what's in the selected drive
350 if (iconSelected >= 1 && iconSelected <= 2)
352 if (!floppyDrive.IsEmpty(iconSelected - 1))
353 SpawnMessage("\"%s\"", floppyDrive.ImageName(iconSelected - 1));
361 void GUI::HandleIconSelection(SDL_Renderer * renderer)
363 // Set up drive icons in their current states
364 AssembleDriveIcon(renderer, 0);
365 AssembleDriveIcon(renderer, 1);
367 // Reload the background...
368 SDL_UpdateTexture(overlay, NULL, texturePointer, 128 * sizeof(Uint32));
370 if (SDL_SetRenderTarget(renderer, overlay) < 0)
372 WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
376 // Draw the icon selector, if an icon is selected
377 if (iconSelected >= 0)
379 SDL_Rect dst;// = { 54, 54, 24 - 7, 2 };
380 dst.w = dst.h = 54, dst.x = 24 - 7, dst.y = 2 + (iconSelected * 54);
381 SDL_RenderCopy(renderer, iconSelection, NULL, &dst);
384 DrawSidebarIcons(renderer);
386 // Set render target back to default
387 SDL_SetRenderTarget(renderer, NULL);
391 void GUI::AssembleDriveIcon(SDL_Renderer * renderer, int driveNumber)
393 SDL_Texture * drive[2] = { disk1Icon, disk2Icon };
394 const char * number[2] = { numeralOne, numeralTwo };
396 if (SDL_SetRenderTarget(renderer, drive[driveNumber]) < 0)
398 WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
402 SDL_RenderClear(renderer);
403 SDL_RenderCopy(renderer, diskIcon, NULL, NULL);
405 // Drive door @ (16, 7)
407 dst.w = 8, dst.h = 10, dst.x = 16, dst.y = 7;
408 SDL_RenderCopy(renderer, (floppyDrive.IsEmpty(driveNumber) ?
409 doorOpen : doorClosed), NULL, &dst);
411 // Numeral @ (30, 20)
412 DrawCharArray(renderer, number[driveNumber], 30, 20, 7, 7, 0xD0, 0xE0, 0xF0);
413 DrawDriveLight(renderer, driveNumber);
414 DrawEjectButton(renderer, driveNumber);
416 // Set render target back to default
417 SDL_SetRenderTarget(renderer, NULL);
421 void GUI::DrawEjectButton(SDL_Renderer * renderer, int driveNumber)
423 if (floppyDrive.IsEmpty(driveNumber))
426 uint8_t r = 0x00, g = 0xAA, b = 0x00;
428 if ((driveNumber == 0 && disk1EjectHovered)
429 || (driveNumber == 1 && disk2EjectHovered))
430 r = 0x20, g = 0xFF, b = 0x20;
432 DrawCharArray(renderer, ejectIcon, 29, 31, 8, 7, r, g, b);
436 void GUI::DrawDriveLight(SDL_Renderer * renderer, int driveNumber)
438 int lightState = floppyDrive.DriveLightStatus(driveNumber);
439 int r = 0x77, g = 0x00, b = 0x00;
441 if (lightState == DLS_READ)
442 r = 0x20, g = 0xFF, b = 0x20;
443 else if (lightState == DLS_WRITE)
444 r = 0xFF, g = 0x30, b = 0x30;
446 // Drive light @ (8, 21)
447 DrawCharArray(renderer, driveLight, 8, 21, 5, 5, r, g, b);
451 void GUI::DrawCharArray(SDL_Renderer * renderer, const char * array, int x,
452 int y, int w, int h, int r, int g, int b)
454 SDL_SetRenderDrawColor(renderer, r, g, b, 0xFF);
456 for(int j=0; j<h; j++)
458 for(int i=0; i<w; i++)
460 if (array[(j * w) + i] != ' ')
461 SDL_RenderDrawPoint(renderer, x + i, y + j);
465 SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00);
469 void GUI::HandleGUIState(void)
473 if (olDst.x < SIDEBAR_X_POS && sidebarState == SBS_SHOWING)
475 olDst.x = SIDEBAR_X_POS;
476 sidebarState = SBS_SHOWN;
479 else if (olDst.x > VIRTUAL_SCREEN_WIDTH && sidebarState == SBS_HIDING)
481 olDst.x = VIRTUAL_SCREEN_WIDTH;
482 sidebarState = SBS_HIDDEN;
488 void GUI::DrawSidebarIcons(SDL_Renderer * renderer)
490 SDL_Texture * icons[7] = { powerOnIcon, disk1Icon, disk2Icon, diskSwapIcon,
491 stateSaveIcon, stateLoadIcon, configIcon };
493 icons[0] = (powerOnState ? powerOnIcon : powerOffIcon);
496 dst.w = dst.h = 40, dst.x = 24, dst.y = 2 + 7;
498 for(int i=0; i<7; i++)
500 SDL_RenderCopy(renderer, icons[i], NULL, &dst);
506 void GUI::Render(SDL_Renderer * renderer)
513 if (sidebarState != SBS_HIDDEN)
514 HandleIconSelection(renderer);
516 SDL_RenderCopy(renderer, overlay, NULL, &olDst);
519 DiskSelector::Render(renderer);
528 cut into 7 pieces give ~54 pix per piece
529 So, let's try 40x40 icons, and see if that's good enough...
532 drive proportions: 1.62 : 1
545 | ^| <-- eject button
566 maybe state save/load