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;
118 std::vector<FileStruct> hdList;
121 void DiskSelector::Init(SDL_Renderer * renderer)
123 window = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888,
124 SDL_TEXTUREACCESS_TARGET, DS_WIDTH, DS_HEIGHT);
128 WriteLog("GUI (DiskSelector): Could not create window!\n");
132 if (SDL_SetTextureBlendMode(window, SDL_BLENDMODE_BLEND) == -1)
133 WriteLog("GUI (DiskSelector): Could not set blend mode for window.\n");
135 scrollLeftIcon = GUI::CreateTexture(renderer, &scroll_left);
136 scrollRightIcon = GUI::CreateTexture(renderer, &scroll_right);
138 for(uint32_t i=0; i<DS_WIDTH*DS_HEIGHT; i++)
139 windowPixels[i] = 0xEF007F00;
141 SDL_UpdateTexture(window, NULL, windowPixels, DS_WIDTH * sizeof(Uint32));
144 DrawFilenames(renderer);
149 // Find all disks images top level call
151 void DiskSelector::FindDisks(void)
154 FindDisks(settings.disksPath);
155 std::sort(fsList.begin(), fsList.end(), FileStruct());
156 // Calculate the number of columns in the file selector...
157 numColumns = (int)ceilf((float)fsList.size() / 27.0f);
158 WriteLog("GUI (DiskSelector)::FindDisks(): # of columns is %i (%i files)\n", numColumns, fsList.size());
163 OK, so the way that you can determine if a file is a directory in a cross-
164 platform way is to do an opendir() call on a discovered filename. If it
165 returns NULL, then it's a regular file and not a directory. Though I think the
166 Linux method is more elegant. :-P
169 // Find all disks images within path (recursive call does depth first search)
171 void DiskSelector::FindDisks(const char * path)
173 DIR * dir = opendir(path);
177 WriteLog("GUI (DiskSelector)::FindDisks: Could not open directory \"%s\%!\n", path);
183 while ((ent = readdir(dir)) != NULL)
186 sprintf(buf, "%s/%s", path, ent->d_name);
188 // Cross-platform way to test if it's a directory...
189 DIR * test = opendir(buf);
191 // if ((ent->d_type == DT_REG) && HasLegalExtension(ent->d_name))
194 if (HasLegalExtension(ent->d_name))
197 fs.image = ent->d_name;
199 fsList.push_back(fs);
202 // else if (ent->d_type == DT_DIR)
205 // Make sure we close the thing, since it's a bona-fide dir!
208 // Only recurse if the directory is not one of the special ones...
209 if ((strcmp(ent->d_name, "..") != 0)
210 && (strcmp(ent->d_name, ".") != 0))
212 // Check to see if this is a special directory with a manifest
214 sprintf(buf2, "%s/manifest.txt", buf);
215 FILE * fp = fopen(buf2, "r");
217 // No manifest means it's just a regular directory...
222 // Read the manifest and all that good stuff
224 ReadManifest(fp, &fs.diskSet);
227 // Finally, check that the stuff in the manifest is
228 // actually in the directory...
229 if (CheckManifest(buf, &fs.diskSet) == true)
232 fs.image = fs.diskSet.name;
233 fsList.push_back(fs);
236 WriteLog("Manifest for '%s' failed check phase.\n", fs.diskSet.name.c_str());
238 printf("Name found: \"%s\" (%d)\nDisks:\n", fs.diskSet.name.c_str(), fs.diskSet.num);
239 for(int i=0; i<fs.diskSet.num; i++)
240 printf("%s (CRC: %08X)\n", fs.diskSet.image[i].c_str(), fs.diskSet.crc[i]);
252 void DiskSelector::ReadManifest(FILE * fp, DiskSet * ds)
260 fgets(line, 0x10000, fp);
263 if ((line[0] == '#') || (line[0] == '\n'))
264 ; // Do nothing with comments or blank lines...
271 if (strncmp(line, "diskset", 7) == 0)
273 sscanf(line, "diskset=\"%[^\"]\"", buf);
276 else if (strncmp(line, "disks", 5) == 0)
278 sscanf(line, "disks=%hhd", &ds->num);
280 else if (strncmp(line, "disk", 4) == 0)
282 int n = sscanf(line, "disk=%s %s (%s)", buf, crcbuf, altName);
284 if ((n == 2) || (n == 3))
286 ds->image[disksFound] = buf;
287 ds->crc[disksFound] = strtoul(crcbuf, NULL, 16);
291 ds->imgName[disksFound] = altName;
294 // Find the file's extension, if any
295 char * ext = strrchr(buf, '.');
297 // Kill the disk extension, if it exists
301 ds->imgName[disksFound] = buf;
305 WriteLog("Malformed disk descriptor in manifest at line %d\n", lineNo);
310 if (disksFound != ds->num)
311 WriteLog("Found only %d entries in manifest, expected %hhd\n", disksFound, ds->num);
315 bool DiskSelector::CheckManifest(const char * path, DiskSet * ds)
319 for(int i=0; i<ds->num; i++)
321 std::string filename = path;
323 filename += ds->image[i];
325 uint8_t * buf = ReadFile(filename.c_str(), &size);
329 ds->crcFound[i] = CRC32(buf, size);
333 if (ds->crc[i] != ds->crcFound[i])
335 WriteLog("Warning: Bad CRC32 for '%s'. Expected: %08X, found: %08X\n", ds->image[i], ds->crc[i], ds->crcFound[i]);
340 return (found == ds->num ? true : false);
344 bool DiskSelector::HasLegalExtension(const char * name)
346 // Find the file's extension, if any
347 const char * ext = strrchr(name, '.');
349 // No extension, so fuggetaboutit
353 // Otherwise, look for a legal extension
354 // We should be smarter than this, and look at headers & file sizes instead
355 if ((strcasecmp(ext, ".dsk") == 0)
356 || (strcasecmp(ext, ".do") == 0)
357 || (strcasecmp(ext, ".po") == 0)
358 || (strcasecmp(ext, ".woz") == 0))
366 // Find all disks images top level call
368 void DiskSelector::FindHardDisks(void)
371 FindHardDisks(settings.disksPath);
372 std::sort(hdList.begin(), hdList.end(), FileStruct());
373 WriteLog("GUI (DiskSelector)::FindHardDisks(): # of HDs is %i\n", hdList.size());
378 // Find all hard disk images within path (recursive call does depth first search)
380 void DiskSelector::FindHardDisks(const char * path)
382 DIR * dir = opendir(path);
386 WriteLog("GUI (DiskSelector)::FindHardDisks: Could not open directory \"%s\%!\n", path);
392 while ((ent = readdir(dir)) != NULL)
395 sprintf(buf, "%s/%s", path, ent->d_name);
397 // Cross-platform way to test if it's a directory (test = NULL -> file)
398 DIR * test = opendir(buf);
402 const char * ext = strrchr(ent->d_name, '.');
404 if ((ext != NULL) && (strcasecmp(ext, ".2mg") == 0))
407 fs.image = ent->d_name;
409 hdList.push_back(fs);
414 // Make sure we close the thing, since it's a bona-fide dir!
417 // Only recurse if the directory is not one of the special ones...
418 if ((strcmp(ent->d_name, "..") != 0)
419 && (strcmp(ent->d_name, ".") != 0))
430 void DiskSelector::DrawFilenames(SDL_Renderer * renderer)
432 if (SDL_SetRenderTarget(renderer, window) < 0)
434 WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
438 // 3 columns of 16 chars apiece (with 8X16 font), 18 rows
439 // 3 columns of 18 chars apiece (with 7X12 font), 24 rows
440 // 3 columns of 21 chars apiece (with 6X11 font), 27 rows
442 unsigned int count = 0;
443 unsigned int fsStart = colStart * 27;
446 // Draw partial columns (for scrolling left/right)
447 // [could probably combine these...]
448 if (textScrollCount < 0)
450 int partialColStart = (colStart - 1) * 27;
451 offset = -1 * textScrollCount;
453 for(unsigned int y=0; y<27; y++)
455 for(unsigned int i=22+textScrollCount, x=0; i<21; i++, x++)
457 if (i >= fsList[partialColStart + y].image.length())
460 GUI::DrawCharacter(renderer, x + 1, y + 1, fsList[partialColStart + y].image[i], false);
464 else if (textScrollCount > 0)
466 offset = 22 - textScrollCount;
468 for(unsigned int y=0; y<27; y++)
470 for(unsigned int i=textScrollCount, x=0; i<21; i++, x++)
472 if (i >= fsList[fsStart + y].image.length())
475 GUI::DrawCharacter(renderer, x + 1, y + 1, fsList[fsStart + y].image[i], false);
482 while (fsStart < fsList.size())
484 // int currentX = (count / 18) * 17;
485 // int currentY = (count % 18);
486 // int currentX = (count / 24) * 19;
487 // int currentY = (count % 24);
488 int currentX = (count / 27) * 22;
489 int currentY = (count % 27);
491 // for(unsigned int i=0; i<16; i++)
492 // for(unsigned int i=0; i<18; i++)
493 for(unsigned int i=0; i<21; i++)
495 if (i >= fsList[fsStart].image.length())
498 bool invert = (diskSelected == (int)fsStart ? true : false);
499 GUI::DrawCharacter(renderer, currentX + i + 1 + offset, currentY + 1, fsList[fsStart].image[i], invert);
505 // if (count >= (18 * 3))
506 // if (count >= (24 * 3))
507 if (count >= (27 * 3))
511 // If a disk is selected, show it on the top line in inverse video
512 if (diskSelected > -1)
514 for(unsigned int i=0; i<65; i++)
516 if (i >= fsList[diskSelected].image.length())
519 GUI::DrawCharacter(renderer, i + 1, 0, fsList[diskSelected].image[i], true);
523 // Set render target back to default
524 SDL_SetRenderTarget(renderer, NULL);
528 void DiskSelector::ShowWindow(int drive)
530 diskSelectorState = DSS_SHOWN;
537 void DiskSelector::HideWindow(void)
539 diskSelectorState = DSS_HIDDEN;
549 void DiskSelector::MouseDown(int32_t x, int32_t y, uint32_t buttons)
551 if (!showWindow || !entered)
554 if ((diskSelectorState == DSS_LSB_SHOWING) || (diskSelectorState == DSS_LSB_SHOWN))
557 textScrollCount = 21;
561 diskSelectorState = DSS_LSB_HIDING;
568 if ((diskSelectorState == DSS_RSB_SHOWING) || (diskSelectorState == DSS_RSB_SHOWN))
571 textScrollCount = -21;
573 if ((colStart + 3) == numColumns)
575 diskSelectorState = DSS_RSB_HIDING;
582 if (diskSelected != -1)
584 floppyDrive[0].LoadImage(fsList[diskSelected].fullPath.c_str(), driveNumber);
591 void DiskSelector::MouseUp(int32_t x, int32_t y, uint32_t buttons)
599 void DiskSelector::MouseMove(int32_t x, int32_t y, uint32_t buttons)
604 // Check to see if DS has been hovered yet, and, if so, set a flag to show
606 if (!entered && ((x >= DS_XPOS) && (x <= (DS_XPOS + DS_WIDTH))
607 && (y >= DS_YPOS) && (y <= (DS_YPOS + DS_HEIGHT))))
610 // Check to see if the DS, since being hovered, is now no longer being
612 //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...
613 if (entered && ((x < DS_XPOS) || (x > (DS_XPOS + DS_WIDTH))
614 || (y < DS_YPOS) || (y > (DS_YPOS + DS_HEIGHT))))
616 diskSelectorState = DSS_HIDDEN;
626 // Bail out if the DS hasn't been entered yet
632 +-----+---------------------+-----+
635 +-----+---------------------+-----+
637 | | x is here and state is DSS_SHOWN
638 | x is here and state is DSS_LSB_SHOWING or DSS_RSB_SHOWING
639 x is here and state is DSS_SHOWN
642 if (x < (DS_XPOS + SCROLL_HOT_WIDTH))
644 if ((colStart > 0) && (diskSelectorState == DSS_SHOWN))
646 diskSelectorState = DSS_LSB_SHOWING;
650 else if (x > (DS_XPOS + DS_WIDTH - SCROLL_HOT_WIDTH))
652 if (((colStart + 3) < numColumns) && (diskSelectorState == DSS_SHOWN))
654 diskSelectorState = DSS_RSB_SHOWING;
660 // Handle the excluded middle :-P
661 if ((diskSelectorState == DSS_LSB_SHOWING)
662 || (diskSelectorState == DSS_LSB_SHOWN))
664 diskSelectorState = DSS_LSB_HIDING;
667 else if ((diskSelectorState == DSS_RSB_SHOWING)
668 || (diskSelectorState == DSS_RSB_SHOWN))
670 diskSelectorState = DSS_RSB_HIDING;
675 // The -1 terms move the origin to the upper left corner (from 1 in, and 1
677 int xChar = ((x - DS_XPOS) / FONT_WIDTH) - 1;
678 int yChar = ((y - DS_YPOS) / FONT_HEIGHT) - 1;
679 diskSelected = ((xChar / 22) * 27) + yChar + (colStart * 27);
681 if ((yChar < 0) || (yChar >= 27)
682 || (diskSelected >= (int)fsList.size())
683 || (diskSelectorState == DSS_LSB_SHOWING)
684 || (diskSelectorState == DSS_LSB_SHOWN)
685 || (diskSelectorState == DSS_RSB_SHOWING)
686 || (diskSelectorState == DSS_RSB_SHOWN))
689 if (diskSelected != lastDiskSelected)
691 HandleSelection(sdlRenderer);
692 lastDiskSelected = diskSelected;
697 void DiskSelector::HandleGUIState(void)
702 if ((lsbPos > (SCROLL_HOT_WIDTH - 40)) && (diskSelectorState == DSS_LSB_SHOWING))
704 diskSelectorState = DSS_LSB_SHOWN;
705 lsbPos = SCROLL_HOT_WIDTH - 40;
708 else if ((lsbPos < -40) && (diskSelectorState == DSS_LSB_HIDING))
710 diskSelectorState = DSS_SHOWN;
714 else if ((rsbPos < (DS_WIDTH - SCROLL_HOT_WIDTH)) && (diskSelectorState == DSS_RSB_SHOWING))
716 diskSelectorState = DSS_RSB_SHOWN;
717 rsbPos = DS_WIDTH - SCROLL_HOT_WIDTH;
720 else if ((rsbPos > DS_WIDTH) && (diskSelectorState == DSS_RSB_HIDING))
722 diskSelectorState = DSS_SHOWN;
727 if (textScrollCount < 0)
729 textScrollCount += 2;
731 if (textScrollCount > 0)
737 else if (textScrollCount > 0)
739 textScrollCount -= 2;
741 if (textScrollCount < 0)
750 void DiskSelector::HandleSelection(SDL_Renderer * renderer)
752 SDL_UpdateTexture(window, NULL, windowPixels, DS_WIDTH * sizeof(Uint32));
753 DrawFilenames(renderer);
758 void DiskSelector::Render(SDL_Renderer * renderer)
760 if (!(window && showWindow))
765 if (((diskSelectorState != DSS_LSB_SHOWN)
766 && (diskSelectorState != DSS_RSB_SHOWN)
767 && (diskSelectorState != DSS_SHOWN))
768 || (textScrollCount != 0) || refresh)
769 HandleSelection(renderer);
771 // Render scroll arrows (need to figure out why no alpha!)
772 SDL_SetRenderTarget(renderer, window);
773 SDL_Rect dst2 = { 0, ((DS_HEIGHT - 40) / 2), 40, 40 };
775 SDL_RenderCopy(renderer, scrollLeftIcon, NULL, &dst2);
776 SDL_Rect dst3 = { 0, ((DS_HEIGHT - 40) / 2), 40, 40 };
778 SDL_RenderCopy(renderer, scrollRightIcon, NULL, &dst3);
779 SDL_SetRenderTarget(renderer, NULL);
781 SDL_Rect dst = { DS_XPOS, DS_YPOS, DS_WIDTH, DS_HEIGHT };
782 SDL_RenderCopy(renderer, window, NULL, &dst);