4 // Floppy disk selector GUI
6 // © 2014-2018 Underground Software
8 // JLH = James Hammons <jlhamm@acm.org>
11 // --- ---------- -----------------------------------------------------------
12 // JLH 10/13/2013 Created this file
16 // - Fix bug where hovering on scroll image causes it to fly across the screen
20 #include "diskselector.h"
27 #include "floppydrive.h"
34 // Icons, in GIMP "C" format
35 #include "gfx/scroll-left.c"
36 #include "gfx/scroll-right.c"
42 unsigned int bytesPerPixel; // 3:RGB, 4:RGBA
43 unsigned char pixelData[];
46 enum { DSS_SHOWING, DSS_HIDING, DSS_SHOWN, DSS_HIDDEN, DSS_LSB_SHOWING, DSS_LSB_SHOWN, DSS_LSB_HIDING, DSS_RSB_SHOWING, DSS_RSB_SHOWN, DSS_RSB_HIDING, DSS_TEXT_SCROLLING };
50 #define SCROLL_HOT_WIDTH 48
51 #define DS_XPOS ((VIRTUAL_SCREEN_WIDTH - DS_WIDTH) / 2)
52 #define DS_YPOS ((VIRTUAL_SCREEN_HEIGHT - DS_HEIGHT) / 2)
55 static bool entered = false;
56 static int driveNumber;
57 static int diskSelectorState = DSS_HIDDEN;
58 static int diskSelected = -1;
59 static int lastDiskSelected = -1;
60 static int numColumns;
61 static int colStart = 0;
62 static int dxLeft = 0;
63 static int dxRight = 0;
64 static int rsbPos = DS_WIDTH;
65 static int lsbPos = -40;
66 static int textScrollCount = 0;
67 static bool refresh = false;
70 So, how this will work for multiple columns, where the number of columns is greater than 3, is to have an arrow button pop up on the left or right hand side (putting the mouse on the left or right side of the disk selector activates (shows) the button, if such a move can be made. Button hides when the mouse moves out of the hot zone or when it has no more effect.
74 // We make provision for sets of 32 or less...
76 The way the manifests are laid out, we make the assumption that the boot disk of a set is always listed first. Therefore, image[0] will always be the boot disk.
80 uint8_t num; // # of disks in this set
81 std::string name; // The name of this disk set
82 // std::string fullPath; // The path to the containing folder
83 std::string image[32]; // List of disk images in this set
84 std::string imgName[32];// List of human readable names of disk images
85 uint32_t crc[32]; // List of CRC32s of the disk images in the set
86 uint32_t crcFound[32]; // List of CRC32s actually discovered on filesystem
93 // Struct to hold filenames & full paths to same
101 // FileStruct(): diskSet(NULL) {}
102 // ~FileStruct() { if (diskSet != NULL) delete diskSet; }
104 // Functor, to presumably make the std::sort go faster
105 bool operator()(const FileStruct & a, const FileStruct & b) const
107 return (strcasecmp(a.image.c_str(), b.image.c_str()) < 0 ? true : false);
112 static SDL_Texture * window = NULL;
113 static uint32_t windowPixels[DS_WIDTH * DS_HEIGHT];
114 SDL_Texture * scrollLeftIcon = NULL;
115 SDL_Texture * scrollRightIcon = NULL;
116 bool DiskSelector::showWindow = false;
117 std::vector<FileStruct> fsList;
120 void DiskSelector::Init(SDL_Renderer * renderer)
122 window = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888,
123 SDL_TEXTUREACCESS_TARGET, DS_WIDTH, DS_HEIGHT);
127 WriteLog("GUI (DiskSelector): Could not create window!\n");
131 if (SDL_SetTextureBlendMode(window, SDL_BLENDMODE_BLEND) == -1)
132 WriteLog("GUI (DiskSelector): Could not set blend mode for window.\n");
134 scrollLeftIcon = GUI::CreateTexture(renderer, &scroll_left);
135 scrollRightIcon = GUI::CreateTexture(renderer, &scroll_right);
137 for(uint32_t i=0; i<DS_WIDTH*DS_HEIGHT; i++)
138 windowPixels[i] = 0xEF007F00;
140 SDL_UpdateTexture(window, NULL, windowPixels, DS_WIDTH * sizeof(Uint32));
142 DrawFilenames(renderer);
147 // Find all disks images top level call
149 void DiskSelector::FindDisks(void)
152 FindDisks(settings.disksPath);
153 std::sort(fsList.begin(), fsList.end(), FileStruct());
154 // Calculate the number of columns in the file selector...
155 numColumns = (int)ceilf((float)fsList.size() / 27.0f);
156 WriteLog("GUI (DiskSelector)::FindDisks(): # of columns is %i (%i files)\n", numColumns, fsList.size());
160 OK, so the way that you can determine if a file is a directory in a cross-platform way is to do an opendir() call on a discovered filename. If it returns NULL, then it's a regular file and not a directory. Though I think the Linux method is more elegant. :-P
163 // Find all disks images within path (recursive call does depth first search)
165 void DiskSelector::FindDisks(const char * path)
167 DIR * dir = opendir(path);
171 WriteLog("GUI (DiskSelector)::FindDisks: Could not open directory \"%s\%!\n", path);
177 while ((ent = readdir(dir)) != NULL)
180 sprintf(buf, "%s/%s", path, ent->d_name);
182 // Cross-platform way to test if it's a directory...
183 DIR * test = opendir(buf);
185 // if ((ent->d_type == DT_REG) && HasLegalExtension(ent->d_name))
188 if (HasLegalExtension(ent->d_name))
191 fs.image = ent->d_name;
193 fsList.push_back(fs);
196 // else if (ent->d_type == DT_DIR)
199 // Make sure we close the thing, since it's a bona-fide dir!
202 // Only recurse if the directory is not one of the special ones...
203 if ((strcmp(ent->d_name, "..") != 0)
204 && (strcmp(ent->d_name, ".") != 0))
206 // Check to see if this is a special directory with a manifest
208 sprintf(buf2, "%s/manifest.txt", buf);
209 FILE * fp = fopen(buf2, "r");
211 // No manifest means it's just a regular directory...
216 // Read the manifest and all that good stuff
218 ReadManifest(fp, &fs.diskSet);
221 // Finally, check that the stuff in the manifest is
222 // actually in the directory...
223 if (CheckManifest(buf, &fs.diskSet) == true)
226 fs.image = fs.diskSet.name;
227 fsList.push_back(fs);
230 WriteLog("Manifest for '%s' failed check phase.\n", fs.diskSet.name.c_str());
232 printf("Name found: \"%s\" (%d)\nDisks:\n", fs.diskSet.name.c_str(), fs.diskSet.num);
233 for(int i=0; i<fs.diskSet.num; i++)
234 printf("%s (CRC: %08X)\n", fs.diskSet.image[i].c_str(), fs.diskSet.crc[i]);
246 void DiskSelector::ReadManifest(FILE * fp, DiskSet * ds)
254 fgets(line, 0x10000, fp);
257 if ((line[0] == '#') || (line[0] == '\n'))
258 ; // Do nothing with comments or blank lines...
265 if (strncmp(line, "diskset", 7) == 0)
267 sscanf(line, "diskset=\"%[^\"]\"", buf);
270 else if (strncmp(line, "disks", 5) == 0)
272 sscanf(line, "disks=%hhd", &ds->num);
274 else if (strncmp(line, "disk", 4) == 0)
276 int n = sscanf(line, "disk=%s %s (%s)", buf, crcbuf, altName);
278 if ((n == 2) || (n == 3))
280 ds->image[disksFound] = buf;
281 ds->crc[disksFound] = strtoul(crcbuf, NULL, 16);
285 ds->imgName[disksFound] = altName;
288 // Find the file's extension, if any
289 char * ext = strrchr(buf, '.');
291 // Kill the disk extension, if it exists
295 ds->imgName[disksFound] = buf;
299 WriteLog("Malformed disk descriptor in manifest at line %d\n", lineNo);
304 if (disksFound != ds->num)
305 WriteLog("Found only %d entries in manifest, expected %hhd\n", disksFound, ds->num);
309 bool DiskSelector::CheckManifest(const char * path, DiskSet * ds)
313 for(int i=0; i<ds->num; i++)
315 std::string filename = path;
317 filename += ds->image[i];
319 uint8_t * buf = ReadFile(filename.c_str(), &size);
323 ds->crcFound[i] = CRC32(buf, size);
327 if (ds->crc[i] != ds->crcFound[i])
329 WriteLog("Warning: Bad CRC32 for '%s'. Expected: %08X, found: %08X\n", ds->image[i], ds->crc[i], ds->crcFound[i]);
334 return (found == ds->num ? true : false);
338 bool DiskSelector::HasLegalExtension(const char * name)
340 // Find the file's extension, if any
341 const char * ext = strrchr(name, '.');
343 // No extension, so fuggetaboutit
347 // Otherwise, look for a legal extension
348 // We should be smarter than this, and look at headers & file sizes instead
349 if ((strcasecmp(ext, ".dsk") == 0)
350 || (strcasecmp(ext, ".do") == 0)
351 || (strcasecmp(ext, ".po") == 0)
352 || (strcasecmp(ext, ".woz") == 0))
359 void DiskSelector::DrawFilenames(SDL_Renderer * renderer)
361 if (SDL_SetRenderTarget(renderer, window) < 0)
363 WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
367 // 3 columns of 16 chars apiece (with 8X16 font), 18 rows
368 // 3 columns of 18 chars apiece (with 7X12 font), 24 rows
369 // 3 columns of 21 chars apiece (with 6X11 font), 27 rows
371 unsigned int count = 0;
372 unsigned int fsStart = colStart * 27;
375 // Draw partial columns (for scrolling left/right)
376 // [could probably combine these...]
377 if (textScrollCount < 0)
379 int partialColStart = (colStart - 1) * 27;
380 offset = -1 * textScrollCount;
382 for(unsigned int y=0; y<27; y++)
384 for(unsigned int i=22+textScrollCount, x=0; i<21; i++, x++)
386 if (i >= fsList[partialColStart + y].image.length())
389 GUI::DrawCharacter(renderer, x + 1, y + 1, fsList[partialColStart + y].image[i], false);
393 else if (textScrollCount > 0)
395 offset = 22 - textScrollCount;
397 for(unsigned int y=0; y<27; y++)
399 for(unsigned int i=textScrollCount, x=0; i<21; i++, x++)
401 if (i >= fsList[fsStart + y].image.length())
404 GUI::DrawCharacter(renderer, x + 1, y + 1, fsList[fsStart + y].image[i], false);
411 while (fsStart < fsList.size())
413 // int currentX = (count / 18) * 17;
414 // int currentY = (count % 18);
415 // int currentX = (count / 24) * 19;
416 // int currentY = (count % 24);
417 int currentX = (count / 27) * 22;
418 int currentY = (count % 27);
420 // for(unsigned int i=0; i<16; i++)
421 // for(unsigned int i=0; i<18; i++)
422 for(unsigned int i=0; i<21; i++)
424 if (i >= fsList[fsStart].image.length())
427 bool invert = (diskSelected == (int)fsStart ? true : false);
428 GUI::DrawCharacter(renderer, currentX + i + 1 + offset, currentY + 1, fsList[fsStart].image[i], invert);
434 // if (count >= (18 * 3))
435 // if (count >= (24 * 3))
436 if (count >= (27 * 3))
440 // If a disk is selected, show it on the top line in inverse video
441 if (diskSelected > -1)
443 for(unsigned int i=0; i<65; i++)
445 if (i >= fsList[diskSelected].image.length())
448 GUI::DrawCharacter(renderer, i + 1, 0, fsList[diskSelected].image[i], true);
452 // Set render target back to default
453 SDL_SetRenderTarget(renderer, NULL);
457 void DiskSelector::ShowWindow(int drive)
459 diskSelectorState = DSS_SHOWN;
466 void DiskSelector::HideWindow(void)
468 diskSelectorState = DSS_HIDDEN;
478 void DiskSelector::MouseDown(int32_t x, int32_t y, uint32_t buttons)
480 if (!showWindow || !entered)
483 if ((diskSelectorState == DSS_LSB_SHOWING) || (diskSelectorState == DSS_LSB_SHOWN))
486 textScrollCount = 21;
490 diskSelectorState = DSS_LSB_HIDING;
497 if ((diskSelectorState == DSS_RSB_SHOWING) || (diskSelectorState == DSS_RSB_SHOWN))
500 textScrollCount = -21;
502 if ((colStart + 3) == numColumns)
504 diskSelectorState = DSS_RSB_HIDING;
511 if (diskSelected != -1)
513 floppyDrive[0].LoadImage(fsList[diskSelected].fullPath.c_str(), driveNumber);
520 void DiskSelector::MouseUp(int32_t x, int32_t y, uint32_t buttons)
528 void DiskSelector::MouseMove(int32_t x, int32_t y, uint32_t buttons)
533 // Check to see if DS has been hovered yet, and, if so, set a flag to show
535 if (!entered && ((x >= DS_XPOS) && (x <= (DS_XPOS + DS_WIDTH))
536 && (y >= DS_YPOS) && (y <= (DS_YPOS + DS_HEIGHT))))
539 // Check to see if the DS, since being hovered, is now no longer being
541 //N.B.: Should probably make like a 1/2 to 1 second timeout to allow for overshooting the edge of the thing, maybe have the window fade out gradually and let it come back if you enter before it leaves...
542 if (entered && ((x < DS_XPOS) || (x > (DS_XPOS + DS_WIDTH))
543 || (y < DS_YPOS) || (y > (DS_YPOS + DS_HEIGHT))))
545 diskSelectorState = DSS_HIDDEN;
555 // Bail out if the DS hasn't been entered yet
561 +-----+---------------------+-----+
564 +-----+---------------------+-----+
566 | | x is here and state is DSS_SHOWN
567 | x is here and state is DSS_LSB_SHOWING or DSS_RSB_SHOWING
568 x is here and state is DSS_SHOWN
571 if (x < (DS_XPOS + SCROLL_HOT_WIDTH))
573 if ((colStart > 0) && (diskSelectorState == DSS_SHOWN))
575 diskSelectorState = DSS_LSB_SHOWING;
579 else if (x > (DS_XPOS + DS_WIDTH - SCROLL_HOT_WIDTH))
581 if (((colStart + 3) < numColumns) && (diskSelectorState == DSS_SHOWN))
583 diskSelectorState = DSS_RSB_SHOWING;
589 // Handle the excluded middle :-P
590 if ((diskSelectorState == DSS_LSB_SHOWING)
591 || (diskSelectorState == DSS_LSB_SHOWN))
593 diskSelectorState = DSS_LSB_HIDING;
596 else if ((diskSelectorState == DSS_RSB_SHOWING)
597 || (diskSelectorState == DSS_RSB_SHOWN))
599 diskSelectorState = DSS_RSB_HIDING;
604 // The -1 terms move the origin to the upper left corner (from 1 in, and 1
606 int xChar = ((x - DS_XPOS) / FONT_WIDTH) - 1;
607 int yChar = ((y - DS_YPOS) / FONT_HEIGHT) - 1;
608 diskSelected = ((xChar / 22) * 27) + yChar + (colStart * 27);
610 if ((yChar < 0) || (yChar >= 27)
611 || (diskSelected >= (int)fsList.size())
612 || (diskSelectorState == DSS_LSB_SHOWING)
613 || (diskSelectorState == DSS_LSB_SHOWN)
614 || (diskSelectorState == DSS_RSB_SHOWING)
615 || (diskSelectorState == DSS_RSB_SHOWN))
618 if (diskSelected != lastDiskSelected)
620 HandleSelection(sdlRenderer);
621 lastDiskSelected = diskSelected;
626 void DiskSelector::HandleGUIState(void)
631 if ((lsbPos > (SCROLL_HOT_WIDTH - 40)) && (diskSelectorState == DSS_LSB_SHOWING))
633 diskSelectorState = DSS_LSB_SHOWN;
634 lsbPos = SCROLL_HOT_WIDTH - 40;
637 else if ((lsbPos < -40) && (diskSelectorState == DSS_LSB_HIDING))
639 diskSelectorState = DSS_SHOWN;
643 else if ((rsbPos < (DS_WIDTH - SCROLL_HOT_WIDTH)) && (diskSelectorState == DSS_RSB_SHOWING))
645 diskSelectorState = DSS_RSB_SHOWN;
646 rsbPos = DS_WIDTH - SCROLL_HOT_WIDTH;
649 else if ((rsbPos > DS_WIDTH) && (diskSelectorState == DSS_RSB_HIDING))
651 diskSelectorState = DSS_SHOWN;
656 if (textScrollCount < 0)
658 textScrollCount += 2;
660 if (textScrollCount > 0)
666 else if (textScrollCount > 0)
668 textScrollCount -= 2;
670 if (textScrollCount < 0)
679 void DiskSelector::HandleSelection(SDL_Renderer * renderer)
681 SDL_UpdateTexture(window, NULL, windowPixels, DS_WIDTH * sizeof(Uint32));
682 DrawFilenames(renderer);
687 void DiskSelector::Render(SDL_Renderer * renderer)
689 if (!(window && showWindow))
694 if (((diskSelectorState != DSS_LSB_SHOWN)
695 && (diskSelectorState != DSS_RSB_SHOWN)
696 && (diskSelectorState != DSS_SHOWN))
697 || (textScrollCount != 0) || refresh)
698 HandleSelection(renderer);
700 // Render scroll arrows (need to figure out why no alpha!)
701 SDL_SetRenderTarget(renderer, window);
702 SDL_Rect dst2 = { 0, ((DS_HEIGHT - 40) / 2), 40, 40 };
704 SDL_RenderCopy(renderer, scrollLeftIcon, NULL, &dst2);
705 SDL_Rect dst3 = { 0, ((DS_HEIGHT - 40) / 2), 40, 40 };
707 SDL_RenderCopy(renderer, scrollRightIcon, NULL, &dst3);
708 SDL_SetRenderTarget(renderer, NULL);
710 SDL_Rect dst = { DS_XPOS, DS_YPOS, DS_WIDTH, DS_HEIGHT };
711 SDL_RenderCopy(renderer, window, NULL, &dst);