]> Shamusworld >> Repos - apple2/blob - src/gui/diskselector.cpp
Added initial emulator configuration window, cleanup of settings code.
[apple2] / src / gui / diskselector.cpp
1 //
2 // diskselector.cpp
3 //
4 // Floppy disk selector GUI
5 // by James Hammons
6 // © 2014-2018 Underground Software
7 //
8 // JLH = James Hammons <jlhamm@acm.org>
9 //
10 // WHO  WHEN        WHAT
11 // ---  ----------  -----------------------------------------------------------
12 // JLH  10/13/2013  Created this file
13 //
14 // STILL TO DO:
15 //
16 // - Fix bug where hovering on scroll image causes it to fly across the screen
17 //   [DONE]
18 //
19
20 #include "diskselector.h"
21 #include <dirent.h>
22 #include <algorithm>
23 #include <string>
24 #include <vector>
25 #include "crc32.h"
26 #include "fileio.h"
27 #include "floppydrive.h"
28 #include "font10pt.h"
29 #include "gui.h"
30 #include "log.h"
31 #include "settings.h"
32 #include "video.h"
33
34 // Icons, in GIMP "C" format
35 #include "gfx/scroll-left.c"
36 #include "gfx/scroll-right.c"
37
38
39 struct Bitmap {
40         unsigned int width;
41         unsigned int height;
42         unsigned int bytesPerPixel;                                     // 3:RGB, 4:RGBA
43         unsigned char pixelData[];
44 };
45
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 };
47
48 #define DS_WIDTH                        402
49 #define DS_HEIGHT                       322
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)
53
54
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;
68
69 /*
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.
71 */
72
73
74 // We make provision for sets of 32 or less...
75 /*
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.
77 */
78 struct DiskSet
79 {
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
87
88         DiskSet(): num(0) {}
89 };
90
91
92 //
93 // Struct to hold filenames & full paths to same
94 //
95 struct FileStruct
96 {
97         std::string image;
98         std::string fullPath;
99         DiskSet diskSet;
100
101 //      FileStruct(): diskSet(NULL) {}
102 //      ~FileStruct() { if (diskSet != NULL) delete diskSet; }
103
104         // Functor, to presumably make the std::sort go faster
105         bool operator()(const FileStruct & a, const FileStruct & b) const
106         {
107                 return (strcasecmp(a.image.c_str(), b.image.c_str()) < 0 ? true : false);
108         }
109 };
110
111
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
119
120 void DiskSelector::Init(SDL_Renderer * renderer)
121 {
122         window = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888,
123                 SDL_TEXTUREACCESS_TARGET, DS_WIDTH, DS_HEIGHT);
124
125         if (!window)
126         {
127                 WriteLog("GUI (DiskSelector): Could not create window!\n");
128                 return;
129         }
130
131         if (SDL_SetTextureBlendMode(window, SDL_BLENDMODE_BLEND) == -1)
132                 WriteLog("GUI (DiskSelector): Could not set blend mode for window.\n");
133
134         scrollLeftIcon  = GUI::CreateTexture(renderer, &scroll_left);
135         scrollRightIcon = GUI::CreateTexture(renderer, &scroll_right);
136
137         for(uint32_t i=0; i<DS_WIDTH*DS_HEIGHT; i++)
138                 windowPixels[i] = 0xEF007F00;
139
140         SDL_UpdateTexture(window, NULL, windowPixels, DS_WIDTH * sizeof(Uint32));
141         FindDisks();
142         DrawFilenames(renderer);
143 }
144
145
146 //
147 // Find all disks images top level call
148 //
149 void DiskSelector::FindDisks(void)
150 {
151         fsList.clear();
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());
157 }
158
159 /*
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
161 */
162 //
163 // Find all disks images within path (recursive call does depth first search)
164 //
165 void DiskSelector::FindDisks(const char * path)
166 {
167         DIR * dir = opendir(path);
168
169         if (!dir)
170         {
171                 WriteLog("GUI (DiskSelector)::FindDisks: Could not open directory \"%s\%!\n", path);
172                 return;
173         }
174
175         dirent * ent;
176
177         while ((ent = readdir(dir)) != NULL)
178         {
179                 char buf[0x10000];
180                 sprintf(buf, "%s/%s", path, ent->d_name);
181
182                 // Cross-platform way to test if it's a directory...
183                 DIR * test = opendir(buf);
184
185 //              if ((ent->d_type == DT_REG) && HasLegalExtension(ent->d_name))
186                 if (test == NULL)
187                 {
188                         if (HasLegalExtension(ent->d_name))
189                         {
190                                 FileStruct fs;
191                                 fs.image = ent->d_name;
192                                 fs.fullPath = buf;
193                                 fsList.push_back(fs);
194                         }
195                 }
196 //              else if (ent->d_type == DT_DIR)
197                 else
198                 {
199                         // Make sure we close the thing, since it's a bona-fide dir!
200                         closedir(test);
201
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))
205                         {
206                                 // Check to see if this is a special directory with a manifest
207                                 char buf2[0x10000];
208                                 sprintf(buf2, "%s/manifest.txt", buf);
209                                 FILE * fp = fopen(buf2, "r");
210
211                                 // No manifest means it's just a regular directory...
212                                 if (fp == NULL)
213                                         FindDisks(buf);
214                                 else
215                                 {
216                                         // Read the manifest and all that good stuff
217                                         FileStruct fs;
218                                         ReadManifest(fp, &fs.diskSet);
219                                         fclose(fp);
220
221                                         // Finally, check that the stuff in the manifest is
222                                         // actually in the directory...
223                                         if (CheckManifest(buf, &fs.diskSet) == true)
224                                         {
225                                                 fs.fullPath = buf;
226                                                 fs.image = fs.diskSet.name;
227                                                 fsList.push_back(fs);
228                                         }
229                                         else
230                                                 WriteLog("Manifest for '%s' failed check phase.\n", fs.diskSet.name.c_str());
231 #if 0
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]);
235 #endif
236
237                                 }
238                         }
239                 }
240         }
241
242         closedir(dir);
243 }
244
245
246 void DiskSelector::ReadManifest(FILE * fp, DiskSet * ds)
247 {
248         char line[0x10000];
249         int disksFound = 0;
250         int lineNo = 0;
251
252         while (!feof(fp))
253         {
254                 fgets(line, 0x10000, fp);
255                 lineNo++;
256
257                 if ((line[0] == '#') || (line[0] == '\n'))
258                         ; // Do nothing with comments or blank lines...
259                 else
260                 {
261                         char buf[1024];
262                         char crcbuf[16];
263                         char altName[1024];
264
265                         if (strncmp(line, "diskset", 7) == 0)
266                         {
267                                 sscanf(line, "diskset=\"%[^\"]\"", buf);
268                                 ds->name = buf;
269                         }
270                         else if (strncmp(line, "disks", 5) == 0)
271                         {
272                                 sscanf(line, "disks=%hhd", &ds->num);
273                         }
274                         else if (strncmp(line, "disk", 4) == 0)
275                         {
276                                 int n = sscanf(line, "disk=%s %s (%s)", buf, crcbuf, altName);
277
278                                 if ((n == 2) || (n == 3))
279                                 {
280                                         ds->image[disksFound] = buf;
281                                         ds->crc[disksFound] = strtoul(crcbuf, NULL, 16);
282                                         disksFound++;
283
284                                         if (n == 3)
285                                                 ds->imgName[disksFound] = altName;
286                                         else
287                                         {
288                                                 // Find the file's extension, if any
289                                                 char * ext = strrchr(buf, '.');
290
291                                                 // Kill the disk extension, if it exists
292                                                 if (ext != NULL)
293                                                         *ext = 0;
294
295                                                 ds->imgName[disksFound] = buf;
296                                         }
297                                 }
298                                 else
299                                         WriteLog("Malformed disk descriptor in manifest at line %d\n", lineNo);
300                         }
301                 }
302         }
303
304         if (disksFound != ds->num)
305                 WriteLog("Found only %d entries in manifest, expected %hhd\n", disksFound, ds->num);
306 }
307
308
309 bool DiskSelector::CheckManifest(const char * path, DiskSet * ds)
310 {
311         uint8_t found = 0;
312
313         for(int i=0; i<ds->num; i++)
314         {
315                 std::string filename = path;
316                 filename += "/";
317                 filename += ds->image[i];
318                 uint32_t size;
319                 uint8_t * buf = ReadFile(filename.c_str(), &size);
320
321                 if (buf != NULL)
322                 {
323                         ds->crcFound[i] = CRC32(buf, size);
324                         free(buf);
325                         found++;
326
327                         if (ds->crc[i] != ds->crcFound[i])
328                         {
329                                 WriteLog("Warning: Bad CRC32 for '%s'. Expected: %08X, found: %08X\n", ds->image[i], ds->crc[i], ds->crcFound[i]);
330                         }
331                 }
332         }
333
334         return (found == ds->num ? true : false);
335 }
336
337
338 bool DiskSelector::HasLegalExtension(const char * name)
339 {
340         // Find the file's extension, if any
341         const char * ext = strrchr(name, '.');
342
343         // No extension, so fuggetaboutit
344         if (ext == NULL)
345                 return false;
346
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))
353                 return true;
354
355         return false;
356 }
357
358
359 void DiskSelector::DrawFilenames(SDL_Renderer * renderer)
360 {
361         if (SDL_SetRenderTarget(renderer, window) < 0)
362         {
363                 WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
364                 return;
365         }
366
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
370
371         unsigned int count = 0;
372         unsigned int fsStart = colStart * 27;
373         int offset = 0;
374
375         // Draw partial columns (for scrolling left/right)
376         // [could probably combine these...]
377         if (textScrollCount < 0)
378         {
379                 int partialColStart = (colStart - 1) * 27;
380                 offset = -1 * textScrollCount;
381
382                 for(unsigned int y=0; y<27; y++)
383                 {
384                         for(unsigned int i=22+textScrollCount, x=0; i<21; i++, x++)
385                         {
386                                 if (i >= fsList[partialColStart + y].image.length())
387                                         break;
388
389                                 GUI::DrawCharacter(renderer, x + 1, y + 1, fsList[partialColStart + y].image[i], false);
390                         }
391                 }
392         }
393         else if (textScrollCount > 0)
394         {
395                 offset = 22 - textScrollCount;
396
397                 for(unsigned int y=0; y<27; y++)
398                 {
399                         for(unsigned int i=textScrollCount, x=0; i<21; i++, x++)
400                         {
401                                 if (i >= fsList[fsStart + y].image.length())
402                                         break;
403
404                                 GUI::DrawCharacter(renderer, x + 1, y + 1, fsList[fsStart + y].image[i], false);
405                         }
406                 }
407
408                 fsStart += 27;
409         }
410
411         while (fsStart < fsList.size())
412         {
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);
419
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++)
423                 {
424                         if (i >= fsList[fsStart].image.length())
425                                 break;
426
427                         bool invert = (diskSelected == (int)fsStart ? true : false);
428                         GUI::DrawCharacter(renderer, currentX + i + 1 + offset, currentY + 1, fsList[fsStart].image[i], invert);
429                 }
430
431                 count++;
432                 fsStart++;
433
434 //              if (count >= (18 * 3))
435 //              if (count >= (24 * 3))
436                 if (count >= (27 * 3))
437                         break;
438         }
439
440         // If a disk is selected, show it on the top line in inverse video
441         if (diskSelected > -1)
442         {
443                 for(unsigned int i=0; i<65; i++)
444                 {
445                         if (i >= fsList[diskSelected].image.length())
446                                 break;
447
448                         GUI::DrawCharacter(renderer, i + 1, 0, fsList[diskSelected].image[i], true);
449                 }
450         }
451
452         // Set render target back to default
453         SDL_SetRenderTarget(renderer, NULL);
454 }
455
456
457 void DiskSelector::ShowWindow(int drive)
458 {
459         diskSelectorState = DSS_SHOWN;
460         entered = false;
461         showWindow = true;
462         driveNumber = drive;
463 }
464
465
466 void DiskSelector::HideWindow(void)
467 {
468         diskSelectorState = DSS_HIDDEN;
469         dxLeft = 0;
470         dxRight = 0;
471         rsbPos = DS_WIDTH;
472         lsbPos = -40;
473         showWindow = false;
474         refresh = true;
475 }
476
477
478 void DiskSelector::MouseDown(int32_t x, int32_t y, uint32_t buttons)
479 {
480         if (!showWindow || !entered)
481                 return;
482
483         if ((diskSelectorState == DSS_LSB_SHOWING) || (diskSelectorState == DSS_LSB_SHOWN))
484         {
485                 colStart--;
486                 textScrollCount = 21;
487
488                 if (colStart == 0)
489                 {
490                         diskSelectorState = DSS_LSB_HIDING;
491                         dxLeft = -8;
492                 }
493
494                 return;
495         }
496
497         if ((diskSelectorState == DSS_RSB_SHOWING) || (diskSelectorState == DSS_RSB_SHOWN))
498         {
499                 colStart++;
500                 textScrollCount = -21;
501
502                 if ((colStart + 3) == numColumns)
503                 {
504                         diskSelectorState = DSS_RSB_HIDING;
505                         dxRight = 8;
506                 }
507
508                 return;
509         }
510
511         if (diskSelected != -1)
512         {
513                 floppyDrive[0].LoadImage(fsList[diskSelected].fullPath.c_str(), driveNumber);
514         }
515
516         showWindow = false;
517 }
518
519
520 void DiskSelector::MouseUp(int32_t x, int32_t y, uint32_t buttons)
521 {
522         if (!showWindow)
523                 return;
524
525 }
526
527
528 void DiskSelector::MouseMove(int32_t x, int32_t y, uint32_t buttons)
529 {
530         if (!showWindow)
531                 return;
532
533         // Check to see if DS has been hovered yet, and, if so, set a flag to show
534         // that it has
535         if (!entered && ((x >= DS_XPOS) && (x <= (DS_XPOS + DS_WIDTH))
536                 && (y >= DS_YPOS) && (y <= (DS_YPOS + DS_HEIGHT))))
537                 entered = true;
538
539         // Check to see if the DS, since being hovered, is now no longer being
540         // hovered
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))))
544         {
545                 diskSelectorState = DSS_HIDDEN;
546                 dxLeft = 0;
547                 dxRight = 0;
548                 rsbPos = DS_WIDTH;
549                 lsbPos = -40;
550                 showWindow = false;
551                 refresh = true;
552                 return;
553         }
554
555         // Bail out if the DS hasn't been entered yet
556         if (!entered)
557                 return;
558
559 /*
560 states:
561 +-----+---------------------+-----+
562 |     |                     |     |
563 |     |                     |     |
564 +-----+---------------------+-----+
565  ^           ^                ^
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
569
570 */
571         if (x < (DS_XPOS + SCROLL_HOT_WIDTH))
572         {
573                 if ((colStart > 0) && (diskSelectorState == DSS_SHOWN))
574                 {
575                         diskSelectorState = DSS_LSB_SHOWING;
576                         dxLeft = 8;
577                 }
578         }
579         else if (x > (DS_XPOS + DS_WIDTH - SCROLL_HOT_WIDTH))
580         {
581                 if (((colStart + 3) < numColumns) && (diskSelectorState == DSS_SHOWN))
582                 {
583                         diskSelectorState = DSS_RSB_SHOWING;
584                         dxRight = -8;
585                 }
586         }
587         else
588         {
589                 // Handle the excluded middle  :-P
590                 if ((diskSelectorState == DSS_LSB_SHOWING)
591                         || (diskSelectorState == DSS_LSB_SHOWN))
592                 {
593                         diskSelectorState = DSS_LSB_HIDING;
594                         dxLeft = -8;
595                 }
596                 else if ((diskSelectorState == DSS_RSB_SHOWING)
597                         || (diskSelectorState == DSS_RSB_SHOWN))
598                 {
599                         diskSelectorState = DSS_RSB_HIDING;
600                         dxRight = 8;
601                 }
602         }
603
604         // The -1 terms move the origin to the upper left corner (from 1 in, and 1
605         // down)
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);
609
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))
616                 diskSelected = -1;
617
618         if (diskSelected != lastDiskSelected)
619         {
620                 HandleSelection(sdlRenderer);
621                 lastDiskSelected = diskSelected;
622         }
623 }
624
625
626 void DiskSelector::HandleGUIState(void)
627 {
628         lsbPos += dxLeft;
629         rsbPos += dxRight;
630
631         if ((lsbPos > (SCROLL_HOT_WIDTH - 40)) && (diskSelectorState == DSS_LSB_SHOWING))
632         {
633                 diskSelectorState = DSS_LSB_SHOWN;
634                 lsbPos = SCROLL_HOT_WIDTH - 40;
635                 dxLeft = 0;
636         }
637         else if ((lsbPos < -40) && (diskSelectorState == DSS_LSB_HIDING))
638         {
639                 diskSelectorState = DSS_SHOWN;
640                 lsbPos = -40;
641                 dxLeft = 0;
642         }
643         else if ((rsbPos < (DS_WIDTH - SCROLL_HOT_WIDTH)) && (diskSelectorState == DSS_RSB_SHOWING))
644         {
645                 diskSelectorState = DSS_RSB_SHOWN;
646                 rsbPos = DS_WIDTH - SCROLL_HOT_WIDTH;
647                 dxRight = 0;
648         }
649         else if ((rsbPos > DS_WIDTH) && (diskSelectorState == DSS_RSB_HIDING))
650         {
651                 diskSelectorState = DSS_SHOWN;
652                 rsbPos = DS_WIDTH;
653                 dxRight = 0;
654         }
655
656         if (textScrollCount < 0)
657         {
658                 textScrollCount += 2;
659
660                 if (textScrollCount > 0)
661                 {
662                         textScrollCount = 0;
663                         refresh = true;
664                 }
665         }
666         else if (textScrollCount > 0)
667         {
668                 textScrollCount -= 2;
669
670                 if (textScrollCount < 0)
671                 {
672                         textScrollCount = 0;
673                         refresh = true;
674                 }
675         }
676 }
677
678
679 void DiskSelector::HandleSelection(SDL_Renderer * renderer)
680 {
681         SDL_UpdateTexture(window, NULL, windowPixels, DS_WIDTH * sizeof(Uint32));
682         DrawFilenames(renderer);
683         refresh = false;
684 }
685
686
687 void DiskSelector::Render(SDL_Renderer * renderer)
688 {
689         if (!(window && showWindow))
690                 return;
691
692         HandleGUIState();
693
694         if (((diskSelectorState != DSS_LSB_SHOWN)
695                 && (diskSelectorState != DSS_RSB_SHOWN)
696                 && (diskSelectorState != DSS_SHOWN))
697                 || (textScrollCount != 0) || refresh)
698                 HandleSelection(renderer);
699
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 };
703         dst2.x = lsbPos;
704         SDL_RenderCopy(renderer, scrollLeftIcon, NULL, &dst2);
705         SDL_Rect dst3 = { 0, ((DS_HEIGHT - 40) / 2), 40, 40 };
706         dst3.x = rsbPos;
707         SDL_RenderCopy(renderer, scrollRightIcon, NULL, &dst3);
708         SDL_SetRenderTarget(renderer, NULL);
709
710         SDL_Rect dst = { DS_XPOS, DS_YPOS, DS_WIDTH, DS_HEIGHT };
711         SDL_RenderCopy(renderer, window, NULL, &dst);
712 }
713