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)
57 int diskSelectorState = DSS_HIDDEN;
58 int diskSelected = -1;
59 int lastDiskSelected = -1;
64 int rsbPos = DS_WIDTH;
66 int textScrollCount = 0;
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 SDL_Texture * charStamp = NULL;
114 static uint32_t windowPixels[DS_WIDTH * DS_HEIGHT];
115 static uint32_t stamp[FONT_WIDTH * FONT_HEIGHT];
116 SDL_Texture * scrollLeftIcon = NULL;
117 SDL_Texture * scrollRightIcon = NULL;
118 bool DiskSelector::showWindow = false;
119 std::vector<FileStruct> fsList;
122 void DiskSelector::Init(SDL_Renderer * renderer)
124 window = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888,
125 SDL_TEXTUREACCESS_TARGET, DS_WIDTH, DS_HEIGHT);
126 charStamp = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888,
127 SDL_TEXTUREACCESS_TARGET, FONT_WIDTH, FONT_HEIGHT);
131 WriteLog("GUI (DiskSelector): Could not create window!\n");
135 if (SDL_SetTextureBlendMode(window, SDL_BLENDMODE_BLEND) == -1)
136 WriteLog("GUI (DiskSelector): Could not set blend mode for window.\n");
138 if (SDL_SetTextureBlendMode(charStamp, SDL_BLENDMODE_BLEND) == -1)
139 WriteLog("GUI (DiskSelector): Could not set blend mode for charStamp.\n");
141 scrollLeftIcon = GUI::CreateTexture(renderer, &scroll_left);
142 scrollRightIcon = GUI::CreateTexture(renderer, &scroll_right);
144 for(uint32_t i=0; i<DS_WIDTH*DS_HEIGHT; i++)
145 windowPixels[i] = 0xEF007F00;
147 SDL_UpdateTexture(window, NULL, windowPixels, 128 * sizeof(Uint32));
149 DrawFilenames(renderer);
154 // Find all disks images top level call
156 void DiskSelector::FindDisks(void)
159 FindDisks(settings.disksPath);
160 std::sort(fsList.begin(), fsList.end(), FileStruct());
161 // Calculate the number of columns in the file selector...
162 numColumns = (int)ceilf((float)fsList.size() / 27.0f);
163 WriteLog("GUI (DiskSelector)::FindDisks(): # of columns is %i (%i files)\n", numColumns, fsList.size());
167 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
170 // Find all disks images within path (recursive call does depth first search)
172 void DiskSelector::FindDisks(const char * path)
174 DIR * dir = opendir(path);
178 WriteLog("GUI (DiskSelector)::FindDisks: Could not open directory \"%s\%!\n", path);
184 while ((ent = readdir(dir)) != NULL)
187 sprintf(buf, "%s/%s", path, ent->d_name);
189 // Cross-platform way to test if it's a directory...
190 DIR * test = opendir(buf);
192 // if ((ent->d_type == DT_REG) && HasLegalExtension(ent->d_name))
195 if (HasLegalExtension(ent->d_name))
198 fs.image = ent->d_name;
200 fsList.push_back(fs);
203 // else if (ent->d_type == DT_DIR)
206 // Make sure we close the thing, since it's a bona-fide dir!
209 // Only recurse if the directory is not one of the special ones...
210 if ((strcmp(ent->d_name, "..") != 0)
211 && (strcmp(ent->d_name, ".") != 0))
213 // Check to see if this is a special directory with a manifest
215 sprintf(buf2, "%s/manifest.txt", buf);
216 FILE * fp = fopen(buf2, "r");
218 // No manifest means it's just a regular directory...
223 // Read the manifest and all that good stuff
225 ReadManifest(fp, &fs.diskSet);
228 // Finally, check that the stuff in the manifest is
229 // actually in the directory...
230 if (CheckManifest(buf, &fs.diskSet) == true)
233 fs.image = fs.diskSet.name;
234 fsList.push_back(fs);
237 WriteLog("Manifest for '%s' failed check phase.\n", fs.diskSet.name.c_str());
239 printf("Name found: \"%s\" (%d)\nDisks:\n", fs.diskSet.name.c_str(), fs.diskSet.num);
240 for(int i=0; i<fs.diskSet.num; i++)
241 printf("%s (CRC: %08X)\n", fs.diskSet.image[i].c_str(), fs.diskSet.crc[i]);
253 void DiskSelector::ReadManifest(FILE * fp, DiskSet * ds)
261 fgets(line, 0x10000, fp);
264 if ((line[0] == '#') || (line[0] == '\n'))
265 ; // Do nothing with comments or blank lines...
272 if (strncmp(line, "diskset", 7) == 0)
274 sscanf(line, "diskset=\"%[^\"]\"", buf);
277 else if (strncmp(line, "disks", 5) == 0)
279 sscanf(line, "disks=%hhd", &ds->num);
281 else if (strncmp(line, "disk", 4) == 0)
283 int n = sscanf(line, "disk=%s %s (%s)", buf, crcbuf, altName);
285 if ((n == 2) || (n == 3))
287 ds->image[disksFound] = buf;
288 ds->crc[disksFound] = strtoul(crcbuf, NULL, 16);
292 ds->imgName[disksFound] = altName;
295 // Find the file's extension, if any
296 char * ext = strrchr(buf, '.');
298 // Kill the disk extension, if it exists
302 ds->imgName[disksFound] = buf;
306 WriteLog("Malformed disk descriptor in manifest at line %d\n", lineNo);
311 if (disksFound != ds->num)
312 WriteLog("Found only %d entries in manifest, expected %hhd\n", disksFound, ds->num);
316 bool DiskSelector::CheckManifest(const char * path, DiskSet * ds)
320 for(int i=0; i<ds->num; i++)
322 std::string filename = path;
324 filename += ds->image[i];
326 uint8_t * buf = ReadFile(filename.c_str(), &size);
330 ds->crcFound[i] = CRC32(buf, size);
334 if (ds->crc[i] != ds->crcFound[i])
336 WriteLog("Warning: Bad CRC32 for '%s'. Expected: %08X, found: %08X\n", ds->image[i], ds->crc[i], ds->crcFound[i]);
341 return (found == ds->num ? true : false);
345 bool DiskSelector::HasLegalExtension(const char * name)
347 // Find the file's extension, if any
348 const char * ext = strrchr(name, '.');
350 // No extension, so fuggetaboutit
354 // Otherwise, look for a legal extension
355 // We should be smarter than this, and look at headers & file sizes instead
356 if ((strcasecmp(ext, ".dsk") == 0)
357 || (strcasecmp(ext, ".do") == 0)
358 || (strcasecmp(ext, ".po") == 0)
359 || (strcasecmp(ext, ".woz") == 0))
366 void DiskSelector::DrawFilenames(SDL_Renderer * renderer)
368 if (SDL_SetRenderTarget(renderer, window) < 0)
370 WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
374 // 3 columns of 16 chars apiece (with 8X16 font), 18 rows
375 // 3 columns of 18 chars apiece (with 7X12 font), 24 rows
376 // 3 columns of 21 chars apiece (with 6X11 font), 27 rows
378 unsigned int count = 0;
379 unsigned int fsStart = colStart * 27;
382 // Draw partial columns (for scrolling left/right)
383 // [could probably combine these...]
384 if (textScrollCount < 0)
386 int partialColStart = (colStart - 1) * 27;
387 offset = -1 * textScrollCount;
389 for(unsigned int y=0; y<27; y++)
391 for(unsigned int i=22+textScrollCount, x=0; i<21; i++, x++)
393 if (i >= fsList[partialColStart + y].image.length())
396 DrawCharacter(renderer, x + 1, y + 1, fsList[partialColStart + y].image[i], false);
400 else if (textScrollCount > 0)
402 offset = 22 - textScrollCount;
404 for(unsigned int y=0; y<27; y++)
406 for(unsigned int i=textScrollCount, x=0; i<21; i++, x++)
408 if (i >= fsList[fsStart + y].image.length())
411 DrawCharacter(renderer, x + 1, y + 1, fsList[fsStart + y].image[i], false);
418 while (fsStart < fsList.size())
420 // int currentX = (count / 18) * 17;
421 // int currentY = (count % 18);
422 // int currentX = (count / 24) * 19;
423 // int currentY = (count % 24);
424 int currentX = (count / 27) * 22;
425 int currentY = (count % 27);
427 // for(unsigned int i=0; i<16; i++)
428 // for(unsigned int i=0; i<18; i++)
429 for(unsigned int i=0; i<21; i++)
431 if (i >= fsList[fsStart].image.length())
434 bool invert = (diskSelected == (int)fsStart ? true : false);
435 DrawCharacter(renderer, currentX + i + 1 + offset, currentY + 1, fsList[fsStart].image[i], invert);
441 // if (count >= (18 * 3))
442 // if (count >= (24 * 3))
443 if (count >= (27 * 3))
447 // If a disk is selected, show it on the top line in inverse video
448 if (diskSelected > -1)
450 for(unsigned int i=0; i<65; i++)
452 if (i >= fsList[diskSelected].image.length())
455 DrawCharacter(renderer, i + 1, 0, fsList[diskSelected].image[i], true);
459 // Set render target back to default
460 SDL_SetRenderTarget(renderer, NULL);
464 void DiskSelector::DrawCharacter(SDL_Renderer * renderer, int x, int y, uint8_t c, bool invert/*=false*/)
466 uint32_t inv = (invert ? 0x000000FF : 0x00000000);
467 uint32_t pixel = 0xFFFFC000; // RRGGBBAA
468 uint8_t * ptr = (uint8_t *)&font10pt[(c - 0x20) * FONT_WIDTH * FONT_HEIGHT];
470 dst.x = x * FONT_WIDTH, dst.y = y * FONT_HEIGHT, dst.w = FONT_WIDTH, dst.h = FONT_HEIGHT;
472 for(int i=0; i<FONT_WIDTH*FONT_HEIGHT; i++)
473 stamp[i] = (pixel | ptr[i]) ^ inv;
475 SDL_UpdateTexture(charStamp, NULL, stamp, FONT_WIDTH * sizeof(Uint32));
476 SDL_RenderCopy(renderer, charStamp, NULL, &dst);
480 void DiskSelector::ShowWindow(int drive)
482 diskSelectorState = DSS_SHOWN;
489 void DiskSelector::MouseDown(int32_t x, int32_t y, uint32_t buttons)
491 if (!showWindow || !entered)
494 if ((diskSelectorState == DSS_LSB_SHOWING) || (diskSelectorState == DSS_LSB_SHOWN))
497 textScrollCount = 21;
501 diskSelectorState = DSS_LSB_HIDING;
508 if ((diskSelectorState == DSS_RSB_SHOWING) || (diskSelectorState == DSS_RSB_SHOWN))
511 textScrollCount = -21;
513 if ((colStart + 3) == numColumns)
515 diskSelectorState = DSS_RSB_HIDING;
522 if (diskSelected != -1)
524 floppyDrive[0].LoadImage(fsList[diskSelected].fullPath.c_str(), driveNumber);
531 void DiskSelector::MouseUp(int32_t x, int32_t y, uint32_t buttons)
539 void DiskSelector::MouseMove(int32_t x, int32_t y, uint32_t buttons)
544 // Check to see if DS has been hovered yet, and, if so, set a flag to show
546 if (!entered && ((x >= DS_XPOS) && (x <= (DS_XPOS + DS_WIDTH))
547 && (y >= DS_YPOS) && (y <= (DS_YPOS + DS_HEIGHT))))
550 // Check to see if the DS, since being hovered, is now no longer being
552 //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...
553 if (entered && ((x < DS_XPOS) || (x > (DS_XPOS + DS_WIDTH))
554 || (y < DS_YPOS) || (y > (DS_YPOS + DS_HEIGHT))))
556 diskSelectorState = DSS_HIDDEN;
566 // Bail out if the DS hasn't been entered yet
572 +-----+---------------------+-----+
575 +-----+---------------------+-----+
577 | | x is here and state is DSS_SHOWN
578 | x is here and state is DSS_LSB_SHOWING or DSS_RSB_SHOWING
579 x is here and state is DSS_SHOWN
582 if (x < (DS_XPOS + SCROLL_HOT_WIDTH))
584 if ((colStart > 0) && (diskSelectorState == DSS_SHOWN))
586 diskSelectorState = DSS_LSB_SHOWING;
590 else if (x > (DS_XPOS + DS_WIDTH - SCROLL_HOT_WIDTH))
592 if (((colStart + 3) < numColumns) && (diskSelectorState == DSS_SHOWN))
594 diskSelectorState = DSS_RSB_SHOWING;
600 // Handle the excluded middle :-P
601 if ((diskSelectorState == DSS_LSB_SHOWING)
602 || (diskSelectorState == DSS_LSB_SHOWN))
604 diskSelectorState = DSS_LSB_HIDING;
607 else if ((diskSelectorState == DSS_RSB_SHOWING)
608 || (diskSelectorState == DSS_RSB_SHOWN))
610 diskSelectorState = DSS_RSB_HIDING;
615 // The -1 terms move the origin to the upper left corner (from 1 in, and 1
617 int xChar = ((x - DS_XPOS) / FONT_WIDTH) - 1;
618 int yChar = ((y - DS_YPOS) / FONT_HEIGHT) - 1;
619 diskSelected = ((xChar / 22) * 27) + yChar + (colStart * 27);
621 if ((yChar < 0) || (yChar >= 27)
622 || (diskSelected >= (int)fsList.size())
623 || (diskSelectorState == DSS_LSB_SHOWING)
624 || (diskSelectorState == DSS_LSB_SHOWN)
625 || (diskSelectorState == DSS_RSB_SHOWING)
626 || (diskSelectorState == DSS_RSB_SHOWN))
629 if (diskSelected != lastDiskSelected)
631 HandleSelection(sdlRenderer);
632 lastDiskSelected = diskSelected;
637 void DiskSelector::HandleGUIState(void)
642 if ((lsbPos > (SCROLL_HOT_WIDTH - 40)) && (diskSelectorState == DSS_LSB_SHOWING))
644 diskSelectorState = DSS_LSB_SHOWN;
645 lsbPos = SCROLL_HOT_WIDTH - 40;
648 else if ((lsbPos < -40) && (diskSelectorState == DSS_LSB_HIDING))
650 diskSelectorState = DSS_SHOWN;
654 else if ((rsbPos < (DS_WIDTH - SCROLL_HOT_WIDTH)) && (diskSelectorState == DSS_RSB_SHOWING))
656 diskSelectorState = DSS_RSB_SHOWN;
657 rsbPos = DS_WIDTH - SCROLL_HOT_WIDTH;
660 else if ((rsbPos > DS_WIDTH) && (diskSelectorState == DSS_RSB_HIDING))
662 diskSelectorState = DSS_SHOWN;
667 if (textScrollCount < 0)
669 textScrollCount += 2;
671 if (textScrollCount > 0)
677 else if (textScrollCount > 0)
679 textScrollCount -= 2;
681 if (textScrollCount < 0)
690 void DiskSelector::HandleSelection(SDL_Renderer * renderer)
692 SDL_UpdateTexture(window, NULL, windowPixels, 128 * sizeof(Uint32));
693 DrawFilenames(renderer);
698 void DiskSelector::Render(SDL_Renderer * renderer)
700 if (!(window && showWindow))
705 if (((diskSelectorState != DSS_LSB_SHOWN)
706 && (diskSelectorState != DSS_RSB_SHOWN)
707 && (diskSelectorState != DSS_SHOWN))
708 || (textScrollCount != 0) || refresh)
709 HandleSelection(renderer);
711 // Render scroll arrows (need to figure out why no alpha!)
712 SDL_SetRenderTarget(renderer, window);
713 SDL_Rect dst2 = { 0, ((DS_HEIGHT - 40) / 2), 40, 40 };
715 SDL_RenderCopy(renderer, scrollLeftIcon, NULL, &dst2);
716 SDL_Rect dst3 = { 0, ((DS_HEIGHT - 40) / 2), 40, 40 };
718 SDL_RenderCopy(renderer, scrollRightIcon, NULL, &dst3);
719 SDL_SetRenderTarget(renderer, NULL);
721 SDL_Rect dst = { DS_XPOS, DS_YPOS, DS_WIDTH, DS_HEIGHT };
722 SDL_RenderCopy(renderer, window, NULL, &dst);