]> Shamusworld >> Repos - apple2/blob - src/gui/diskselector.cpp
Improvements to timing, disk selector; added Double LoRes.
[apple2] / src / gui / diskselector.cpp
1 //
2 // diskselector.cpp
3 //
4 // Floppy disk selector GUI
5 // by James Hammons
6 // © 2014 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 //
17
18 #include "diskselector.h"
19 #include <dirent.h>
20 #include <algorithm>
21 #include <string>
22 #include <vector>
23 #include "apple2.h"
24 #include "font10pt.h"
25 #include "gui.h"
26 #include "log.h"
27 #include "settings.h"
28 #include "video.h"
29
30 // Icons, in GIMP "C" format
31 #include "gfx/scroll-left.c"
32 #include "gfx/scroll-right.c"
33
34
35 struct Bitmap {
36         unsigned int width;
37         unsigned int height;
38         unsigned int bytesPerPixel;                                     // 3:RGB, 4:RGBA
39         unsigned char pixelData[];
40 };
41
42 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 };
43
44 #define DS_WIDTH                        402
45 #define DS_HEIGHT                       322
46 #define SCROLL_HOT_WIDTH        48
47 // Need to add logic for left/right scroll buttons (they show when the mouse
48 // is in the left or right hand portion of the rect).
49 #define DS_XPOS ((VIRTUAL_SCREEN_WIDTH - DS_WIDTH) / 2)
50 #define DS_YPOS ((VIRTUAL_SCREEN_HEIGHT - DS_HEIGHT) / 2)
51
52
53 bool entered = false;
54 int driveNumber;
55 int diskSelectorState = DSS_HIDDEN;
56 int diskSelected = -1;
57 int lastDiskSelected = -1;
58 int numColumns;
59 int colStart = 0;
60 int dxLeft = 0;
61 int dxRight = 0;
62 int rsbPos = DS_WIDTH;
63 int lsbPos = -40;
64 int textScrollCount = 0;
65 bool refresh = false;
66
67 /*
68 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.
69 */
70
71
72 //
73 // Struct to hold filenames & full paths to same
74 //
75 struct FileStruct
76 {
77         std::string image;
78         std::string fullPath;
79
80         // Functor, to presumably make the std::sort go faster
81         bool operator()(const FileStruct & a, const FileStruct & b) const
82         {
83                 return (strcasecmp(a.image.c_str(), b.image.c_str()) < 0 ? true : false);
84         }
85 };
86
87
88 static SDL_Texture * window = NULL;
89 static SDL_Texture * charStamp = NULL;
90 static uint32_t windowPixels[DS_WIDTH * DS_HEIGHT];
91 static uint32_t stamp[FONT_WIDTH * FONT_HEIGHT];
92 SDL_Texture * scrollLeftIcon = NULL;
93 SDL_Texture * scrollRightIcon = NULL;
94 bool DiskSelector::showWindow = false;
95 std::vector<FileStruct> fsList;
96
97
98 void DiskSelector::Init(SDL_Renderer * renderer)
99 {
100         window = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888,
101                 SDL_TEXTUREACCESS_TARGET, DS_WIDTH, DS_HEIGHT);
102         charStamp = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888,
103                 SDL_TEXTUREACCESS_TARGET, FONT_WIDTH, FONT_HEIGHT);
104
105         if (!window)
106         {
107                 WriteLog("GUI (DiskSelector): Could not create window!\n");
108                 return;
109         }
110
111         if (SDL_SetTextureBlendMode(window, SDL_BLENDMODE_BLEND) == -1)
112                 WriteLog("GUI (DiskSelector): Could not set blend mode for window.\n");
113
114         if (SDL_SetTextureBlendMode(charStamp, SDL_BLENDMODE_BLEND) == -1)
115                 WriteLog("GUI (DiskSelector): Could not set blend mode for charStamp.\n");
116
117         scrollLeftIcon  = GUI::CreateTexture(renderer, &scroll_left);
118         scrollRightIcon = GUI::CreateTexture(renderer, &scroll_right);
119
120         for(uint32_t i=0; i<DS_WIDTH*DS_HEIGHT; i++)
121                 windowPixels[i] = 0xEF007F00;
122
123         SDL_UpdateTexture(window, NULL, windowPixels, 128 * sizeof(Uint32));
124         FindDisks();
125         DrawFilenames(renderer);
126 }
127
128
129 //
130 // Find all disks images top level call
131 //
132 void DiskSelector::FindDisks(void)
133 {
134         fsList.clear();
135         FindDisks(settings.disksPath);
136         std::sort(fsList.begin(), fsList.end(), FileStruct());
137         // Calculate the number of columns in the file selector...
138         numColumns = (int)ceilf((float)fsList.size() / 27.0f);
139         WriteLog("GUI (DiskSelector)::FindDisks(): # of columns is %i (%i files)\n", numColumns, fsList.size());
140 }
141
142
143 //
144 // Find all disks images within path (recursive call does depth first search)
145 //
146 void DiskSelector::FindDisks(const char * path)
147 {
148         DIR * dir = opendir(path);
149
150         if (!dir)
151         {
152                 WriteLog("GUI (DiskSelector)::FindDisks: Could not open directory \"%s\%!\n", path);
153                 return;
154         }
155
156         dirent * ent;
157
158         while ((ent = readdir(dir)) != NULL)
159         {
160                 char buf[0x10000];
161                 sprintf(buf, "%s/%s", path, ent->d_name);
162
163                 if ((ent->d_type == DT_REG) && HasLegalExtension(ent->d_name))
164                 {
165                         FileStruct fs;
166                         fs.image = ent->d_name;
167                         fs.fullPath = buf;
168                         fsList.push_back(fs);
169                 }
170                 else if (ent->d_type == DT_DIR)
171                 {
172                         // Only recurse if the directory is not one of the special ones...
173                         if ((strcmp(ent->d_name, "..") != 0)
174                                 && (strcmp(ent->d_name, ".") != 0))
175                                 FindDisks(buf);
176                 }
177         }
178
179         closedir(dir);
180 }
181
182
183 bool DiskSelector::HasLegalExtension(const char * name)
184 {
185         // Find the file's extension, if any
186         const char * ext = strrchr(name, '.');
187
188         // No extension, so fuggetaboutit
189         if (ext == NULL)
190                 return false;
191
192         // Otherwise, look for a legal extension
193         // We should be smarter than this, and look at headers & file sizes instead
194         if ((strcasecmp(ext, ".dsk") == 0)
195                 || (strcasecmp(ext, ".do") == 0)
196                 || (strcasecmp(ext, ".po") == 0)
197                 || (strcasecmp(ext, ".nib") == 0))
198                 return true;
199
200         return false;
201 }
202
203
204 void DiskSelector::DrawFilenames(SDL_Renderer * renderer)
205 {
206         if (SDL_SetRenderTarget(renderer, window) < 0)
207         {
208                 WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
209                 return;
210         }
211
212         // 3 columns of 16 chars apiece (with 8X16 font), 18 rows
213         // 3 columns of 18 chars apiece (with 7X12 font), 24 rows
214         // 3 columns of 21 chars apiece (with 6X11 font), 27 rows
215
216         unsigned int count = 0;
217         unsigned int fsStart = colStart * 27;
218         int offset = 0;
219
220         // Draw partial columns (for scrolling left/right)
221         // [could probably combine these...]
222         if (textScrollCount < 0)
223         {
224                 int partialColStart = (colStart - 1) * 27;
225                 offset = -1 * textScrollCount;
226
227                 for(unsigned int y=0; y<27; y++)
228                 {
229                         for(unsigned int i=22+textScrollCount, x=0; i<21; i++, x++)
230                         {
231                                 if (i >= fsList[partialColStart + y].image.length())
232                                         break;
233
234                                 DrawCharacter(renderer, x + 1, y + 1, fsList[partialColStart + y].image[i], false);
235                         }
236                 }
237         }
238         else if (textScrollCount > 0)
239         {
240                 offset = 22 - textScrollCount;
241
242                 for(unsigned int y=0; y<27; y++)
243                 {
244                         for(unsigned int i=textScrollCount, x=0; i<21; i++, x++)
245                         {
246                                 if (i >= fsList[fsStart + y].image.length())
247                                         break;
248
249                                 DrawCharacter(renderer, x + 1, y + 1, fsList[fsStart + y].image[i], false);
250                         }
251                 }
252
253                 fsStart += 27;
254         }
255
256         while (fsStart < fsList.size())
257         {
258 //              int currentX = (count / 18) * 17;
259 //              int currentY = (count % 18);
260 //              int currentX = (count / 24) * 19;
261 //              int currentY = (count % 24);
262                 int currentX = (count / 27) * 22;
263                 int currentY = (count % 27);
264
265 //              for(unsigned int i=0; i<16; i++)
266 //              for(unsigned int i=0; i<18; i++)
267                 for(unsigned int i=0; i<21; i++)
268                 {
269                         if (i >= fsList[fsStart].image.length())
270                                 break;
271
272                         bool invert = (diskSelected == (int)fsStart ? true : false);
273                         DrawCharacter(renderer, currentX + i + 1 + offset, currentY + 1, fsList[fsStart].image[i], invert);
274                 }
275
276                 count++;
277                 fsStart++;
278
279 //              if (count >= (18 * 3))
280 //              if (count >= (24 * 3))
281                 if (count >= (27 * 3))
282                         break;
283         }
284
285         // If a disk is selected, show it on the top line in inverse video
286         if (diskSelected > -1)
287         {
288                 for(unsigned int i=0; i<65; i++)
289                 {
290                         if (i >= fsList[diskSelected].image.length())
291                                 break;
292
293                         DrawCharacter(renderer, i + 1, 0, fsList[diskSelected].image[i], true);
294                 }
295         }
296
297         // Set render target back to default
298         SDL_SetRenderTarget(renderer, NULL);
299 }
300
301
302 void DiskSelector::DrawCharacter(SDL_Renderer * renderer, int x, int y, uint8_t c, bool invert/*=false*/)
303 {
304         uint32_t inv = (invert ? 0x000000FF : 0x00000000);
305         uint32_t pixel = 0xFFFFC000;    // RRGGBBAA
306         uint8_t * ptr = (uint8_t *)&font10pt[(c - 0x20) * FONT_WIDTH * FONT_HEIGHT];
307         SDL_Rect dst;
308         dst.x = x * FONT_WIDTH, dst.y = y * FONT_HEIGHT, dst.w = FONT_WIDTH, dst.h = FONT_HEIGHT;
309
310         for(int i=0; i<FONT_WIDTH*FONT_HEIGHT; i++)
311                 stamp[i] = (pixel | ptr[i]) ^ inv;
312
313         SDL_UpdateTexture(charStamp, NULL, stamp, FONT_WIDTH * sizeof(Uint32));
314         SDL_RenderCopy(renderer, charStamp, NULL, &dst);
315 }
316
317
318 void DiskSelector::ShowWindow(int drive)
319 {
320         entered = false;
321         showWindow = true;
322         driveNumber = drive;
323 }
324
325
326 void DiskSelector::MouseDown(int32_t x, int32_t y, uint32_t buttons)
327 {
328         if (!showWindow)
329                 return;
330
331         if (!entered)
332                 return;
333
334         if ((diskSelectorState == DSS_LSB_SHOWING) || (diskSelectorState == DSS_LSB_SHOWN))
335         {
336                 if (colStart > 0)
337                 {
338                         colStart--;
339                         textScrollCount = 21;
340
341                         if (colStart == 0)
342                         {
343                                 diskSelectorState = DSS_LSB_HIDING;
344                                 dxLeft = -8;
345                         }
346                 }
347
348                 return;
349         }
350
351         if ((diskSelectorState == DSS_RSB_SHOWING) || (diskSelectorState == DSS_RSB_SHOWN))
352         {
353                 if (colStart + 3 < numColumns)
354                 {
355                         colStart++;
356                         textScrollCount = -21;
357
358                         if ((colStart + 3) == numColumns)
359                         {
360                                 diskSelectorState = DSS_RSB_HIDING;
361                                 dxRight = 8;
362                         }
363                 }
364
365                 return;
366         }
367
368         if (diskSelected != -1)
369         {
370                 floppyDrive.LoadImage(fsList[diskSelected].fullPath.c_str(), driveNumber);
371         }
372
373         showWindow = false;
374 }
375
376
377 void DiskSelector::MouseUp(int32_t x, int32_t y, uint32_t buttons)
378 {
379         if (!showWindow)
380                 return;
381
382 }
383
384
385 void DiskSelector::MouseMove(int32_t x, int32_t y, uint32_t buttons)
386 {
387         if (!showWindow)
388                 return;
389
390         // Check to see if DS has been hovered yet, and, if so, set a flag to show
391         // that it has
392         if (!entered && ((x >= DS_XPOS) && (x <= (DS_XPOS + DS_WIDTH))
393                 && (y >= DS_YPOS) && (y <= (DS_YPOS + DS_HEIGHT))))
394                 entered = true;
395
396         // Check to see if the DS, since being hovered, is now no longer being
397         // hovered
398         if (entered && ((x < DS_XPOS) || (x > (DS_XPOS + DS_WIDTH))
399                 || (y < DS_YPOS) || (y > (DS_YPOS + DS_HEIGHT))))
400         {
401                 showWindow = false;
402                 return;
403         }
404
405         if (entered && (colStart > 0))
406         {
407                 if (diskSelectorState != DSS_LSB_SHOWN)
408                 {
409                         if (x < (DS_XPOS + SCROLL_HOT_WIDTH))
410                         {
411                                 diskSelectorState = DSS_LSB_SHOWING;
412                                 dxLeft = 8;
413                         }
414                         else
415                         {
416                                 diskSelectorState = DSS_LSB_HIDING;
417                                 dxLeft = -8;
418                         }
419                 }
420                 else
421                 {
422                         if (x >= (DS_XPOS + SCROLL_HOT_WIDTH))
423                         {
424                                 diskSelectorState = DSS_LSB_HIDING;
425                                 dxLeft = -8;
426                         }
427                 }
428         }
429
430         if (entered && ((colStart + 3) < numColumns))
431         {
432                 if (diskSelectorState != DSS_RSB_SHOWN)
433                 {
434                         if (x > (DS_XPOS + DS_WIDTH - SCROLL_HOT_WIDTH))
435                         {
436                                 diskSelectorState = DSS_RSB_SHOWING;
437                                 dxRight = -8;
438                         }
439                         else
440                         {
441                                 diskSelectorState = DSS_RSB_HIDING;
442                                 dxRight = 8;
443                         }
444                 }
445                 else
446                 {
447                         if (x <= (DS_XPOS + DS_WIDTH - SCROLL_HOT_WIDTH))
448                         {
449                                 diskSelectorState = DSS_RSB_HIDING;
450                                 dxRight = 8;
451                         }
452                 }
453         }
454
455         // The -1 terms move the origin to the upper left corner (from 1 in, and 1
456         // down)
457         int xChar = ((x - DS_XPOS) / FONT_WIDTH) - 1;
458         int yChar = ((y - DS_YPOS) / FONT_HEIGHT) - 1;
459         diskSelected = ((xChar / 22) * 27) + yChar + (colStart * 27);
460
461         if ((yChar < 0) || (yChar >= 27)
462                 || (diskSelected >= (int)fsList.size())
463                 || (diskSelectorState == DSS_LSB_SHOWING)
464                 || (diskSelectorState == DSS_LSB_SHOWN)
465                 || (diskSelectorState == DSS_RSB_SHOWING)
466                 || (diskSelectorState == DSS_RSB_SHOWN))
467                 diskSelected = -1;
468
469         if (diskSelected != lastDiskSelected)
470         {
471                 HandleSelection(sdlRenderer);
472                 lastDiskSelected = diskSelected;
473         }
474 }
475
476
477 void DiskSelector::HandleGUIState(void)
478 {
479         lsbPos += dxLeft;
480         rsbPos += dxRight;
481
482         if ((lsbPos > (SCROLL_HOT_WIDTH - 40)) && (diskSelectorState == DSS_LSB_SHOWING))
483         {
484                 diskSelectorState = DSS_LSB_SHOWN;
485                 lsbPos = SCROLL_HOT_WIDTH - 40;
486                 dxLeft = 0;
487         }
488         else if ((lsbPos < -40) && (diskSelectorState == DSS_LSB_HIDING))
489         {
490                 diskSelectorState = DSS_SHOWN;
491                 lsbPos = -40;
492                 dxLeft = 0;
493         }
494         else if ((rsbPos < (DS_WIDTH - SCROLL_HOT_WIDTH)) && (diskSelectorState == DSS_RSB_SHOWING))
495         {
496                 diskSelectorState = DSS_RSB_SHOWN;
497                 rsbPos = DS_WIDTH - SCROLL_HOT_WIDTH;
498                 dxRight = 0;
499         }
500         else if ((rsbPos > DS_WIDTH) && (diskSelectorState == DSS_RSB_HIDING))
501         {
502                 diskSelectorState = DSS_SHOWN;
503                 rsbPos = DS_WIDTH;
504                 dxRight = 0;
505         }
506
507         if (textScrollCount < 0)
508         {
509                 textScrollCount += 2;
510
511                 if (textScrollCount > 0)
512                 {
513                         textScrollCount = 0;
514                         refresh = true;
515                 }
516         }
517         else if (textScrollCount > 0)
518         {
519                 textScrollCount -= 2;
520
521                 if (textScrollCount < 0)
522                 {
523                         textScrollCount = 0;
524                         refresh = true;
525                 }
526         }
527 }
528
529
530 void DiskSelector::HandleSelection(SDL_Renderer * renderer)
531 {
532         SDL_UpdateTexture(window, NULL, windowPixels, 128 * sizeof(Uint32));
533         DrawFilenames(renderer);
534         refresh = false;
535 }
536
537
538 void DiskSelector::Render(SDL_Renderer * renderer)
539 {
540         if (!(window && showWindow))
541                 return;
542
543         HandleGUIState();
544
545         if (((diskSelectorState != DSS_LSB_SHOWN)
546                 && (diskSelectorState != DSS_RSB_SHOWN)
547                 && (diskSelectorState != DSS_SHOWN))
548                 || (textScrollCount != 0) || refresh)
549                 HandleSelection(renderer);
550
551         // Render scroll arrows (need to figure out why no alpha!)
552         SDL_SetRenderTarget(renderer, window);
553         SDL_Rect dst2 = { 0, ((DS_HEIGHT - 40) / 2), 40, 40 };
554         dst2.x = lsbPos;
555         SDL_RenderCopy(renderer, scrollLeftIcon, NULL, &dst2);
556         SDL_Rect dst3 = { 0, ((DS_HEIGHT - 40) / 2), 40, 40 };
557         dst3.x = rsbPos;
558         SDL_RenderCopy(renderer, scrollRightIcon, NULL, &dst3);
559         SDL_SetRenderTarget(renderer, NULL);
560
561         SDL_Rect dst = { DS_XPOS, DS_YPOS, DS_WIDTH, DS_HEIGHT };
562         SDL_RenderCopy(renderer, window, NULL, &dst);
563 }
564