]> Shamusworld >> Repos - apple2/blob - src/gui/gui.cpp
022c2fdbb53f3288a86b9b6343091d1193d9afe8
[apple2] / src / gui / gui.cpp
1 //
2 // gui.cpp
3 //
4 // Graphical User Interface support
5 // by James Hammons
6 // © 2014 Underground Software
7 //
8 // JLH = James Hammons <jlhamm@acm.org>
9 //
10 // WHO  WHEN        WHAT
11 // ---  ----------  -----------------------------------------------------------
12 // JLH  02/03/2006  Created this file
13 // JLH  03/13/2006  Added functions to allow shutting down GUI externally
14 // JLH  03/22/2006  Finalized basic multiple window support
15 // JLH  03/03/2014  Refactored GUI to use SDL 2, more modern approach as well
16 //
17 // STILL TO DO:
18 //
19 // - Memory leak on quitting with a window active [DONE]
20 // - Multiple window handling [DONE]
21 //
22
23 #include "gui.h"
24 #include "apple2.h"
25 #include "diskselector.h"
26 #include "floppydrive.h"
27 #include "log.h"
28 #include "video.h"
29
30 // Icons, in GIMP "C" format
31 #include "gfx/icon-selection.c"
32 #include "gfx/disk-icon.c"
33 #include "gfx/power-off-icon.c"
34 #include "gfx/power-on-icon.c"
35 #include "gfx/disk-swap-icon.c"
36 #include "gfx/disk-door-open.c"
37 #include "gfx/disk-door-closed.c"
38 #include "gfx/save-state-icon.c"
39 #include "gfx/load-state-icon.c"
40 #include "gfx/config-icon.c"
41
42
43 // Okay, this is ugly but works and I can't think of any better way to handle
44 // this. So what we do when we pass the GIMP bitmaps into a function is pass
45 // them as a (void *) and then cast them as type (Bitmap *) in order to use
46 // them. Yes, it's ugly. Come up with something better!
47
48 struct Bitmap {
49         unsigned int width;
50         unsigned int height;
51         unsigned int bytesPerPixel;                                     // 3:RGB, 4:RGBA
52         unsigned char pixelData[];
53 };
54
55
56 const char numeralOne[(7 * 7) + 1] =
57         "  @@   "
58         " @@@   "
59         "@@@@   "
60         "  @@   "
61         "  @@   "
62         "  @@   "
63         "@@@@@@ ";
64
65 const char numeralTwo[(7 * 7) + 1] =
66         " @@@@@ "
67         "@@   @@"
68         "    @@@"
69         "  @@@@ "
70         " @@@   "
71         "@@     "
72         "@@@@@@@";
73
74 const char ejectIcon[(8 * 7) + 1] =
75         "   @@   "
76         "  @@@@  "
77         " @@@@@@ "
78         "@@@@@@@@"
79         "        "
80         "@@@@@@@@"
81         "@@@@@@@@";
82
83 const char driveLight[(5 * 5) + 1] =
84         " @@@ "
85         "@@@@@"
86         "@@@@@"
87         "@@@@@"
88         " @@@ ";
89
90
91
92 SDL_Texture * GUI::overlay = NULL;
93 SDL_Rect GUI::olDst;
94 int GUI::sidebarState = SBS_HIDDEN;
95 int32_t GUI::dx = 0;
96 int32_t GUI::iconSelected = -1;
97 bool GUI::hasKeyboardFocus = false;
98 bool GUI::powerOnState = true;
99
100 int32_t lastIconSelected = -1;
101 SDL_Texture * iconSelection = NULL;
102 SDL_Texture * diskIcon = NULL;
103 SDL_Texture * disk1Icon = NULL;
104 SDL_Texture * disk2Icon = NULL;
105 SDL_Texture * powerOnIcon = NULL;
106 SDL_Texture * powerOffIcon = NULL;
107 SDL_Texture * diskSwapIcon = NULL;
108 SDL_Texture * stateSaveIcon = NULL;
109 SDL_Texture * stateLoadIcon = NULL;
110 SDL_Texture * configIcon = NULL;
111 SDL_Texture * doorOpen = NULL;
112 SDL_Texture * doorClosed = NULL;
113 uint32_t texturePointer[128 * 380];
114 const char iconHelp[7][80] = { "Turn emulated Apple off/on",
115         "Insert floppy image into drive #1", "Insert floppy image into drive #2",
116         "Swap disks", "Save emulator state", "Load emulator state",
117         "Configure Apple2" };
118 bool disk1EjectHovered = false;
119 bool disk2EjectHovered = false;
120
121
122 #define SIDEBAR_X_POS  (VIRTUAL_SCREEN_WIDTH - 80)
123
124
125 GUI::GUI(void)
126 {
127 }
128
129
130 GUI::~GUI(void)
131 {
132 }
133
134
135 void GUI::Init(SDL_Renderer * renderer)
136 {
137         overlay = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888,
138                 SDL_TEXTUREACCESS_TARGET, 128, 380);
139
140         if (!overlay)
141         {
142                 WriteLog("GUI: Could not create overlay!\n");
143                 return;
144         }
145
146         if (SDL_SetTextureBlendMode(overlay, SDL_BLENDMODE_BLEND) == -1)
147                 WriteLog("GUI: Could not set blend mode for overlay.\n");
148
149         for(uint32_t i=0; i<128*380; i++)
150                 texturePointer[i] = 0xB0A000A0;
151
152         SDL_UpdateTexture(overlay, NULL, texturePointer, 128 * sizeof(Uint32));
153
154         olDst.x = VIRTUAL_SCREEN_WIDTH;
155         olDst.y = 2;
156         olDst.w = 128;
157         olDst.h = 380;
158
159         iconSelection  = CreateTexture(renderer, &icon_selection);
160         diskIcon       = CreateTexture(renderer, &disk_icon);
161         doorOpen       = CreateTexture(renderer, &door_open);
162         doorClosed     = CreateTexture(renderer, &door_closed);
163         disk1Icon      = CreateTexture(renderer, &disk_icon);
164         disk2Icon      = CreateTexture(renderer, &disk_icon);
165         powerOffIcon   = CreateTexture(renderer, &power_off);
166         powerOnIcon    = CreateTexture(renderer, &power_on);
167         diskSwapIcon   = CreateTexture(renderer, &disk_swap);
168         stateSaveIcon  = CreateTexture(renderer, &save_state);
169         stateLoadIcon  = CreateTexture(renderer, &load_state);
170         configIcon     = CreateTexture(renderer, &config);
171
172         // Set up drive icons in their current states
173 //      AssembleDriveIcon(renderer, 0);
174 //      AssembleDriveIcon(renderer, 1);
175
176         if (SDL_SetRenderTarget(renderer, overlay) < 0)
177         {
178                 WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
179         }
180         else
181         {
182                 DrawSidebarIcons(renderer);
183                 // Set render target back to default
184                 SDL_SetRenderTarget(renderer, NULL);
185         }
186
187         DiskSelector::Init(renderer);
188         WriteLog("GUI: Successfully initialized.\n");
189 }
190
191
192 SDL_Texture * GUI::CreateTexture(SDL_Renderer * renderer, const void * source)
193 {
194         Bitmap * bitmap = (Bitmap *)source;
195         SDL_Texture * texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888,
196 //              SDL_TEXTUREACCESS_STATIC, bitmap->width, bitmap->height);
197                 SDL_TEXTUREACCESS_TARGET, bitmap->width, bitmap->height);
198         SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
199         SDL_UpdateTexture(texture, NULL, (Uint32 *)bitmap->pixelData,
200                 bitmap->width * sizeof(Uint32));
201
202         return texture;
203 }
204
205
206 void GUI::MouseDown(int32_t x, int32_t y, uint32_t buttons)
207 {
208         DiskSelector::MouseDown(x, y, buttons);
209
210         if (sidebarState != SBS_SHOWN)
211                 return;
212
213         switch (iconSelected)
214         {
215         // Power
216         case 0:
217                 powerOnState = !powerOnState;
218                 SetPowerState();
219
220                 if (!powerOnState)
221                         SpawnMessage("*** POWER OFF ***");
222
223                 break;
224         // Disk #1
225         case 1:
226                 SpawnMessage("*** DISK #1 ***");
227
228                 if (disk1EjectHovered && !floppyDrive[0].IsEmpty(0))
229                 {
230                         floppyDrive[0].EjectImage(0);
231                         SpawnMessage("*** DISK #1 EJECTED ***");
232                 }
233
234                 if (!disk1EjectHovered)
235                 {
236                         // Load the disk selector
237                         DiskSelector::ShowWindow(0);
238                 }
239
240                 break;
241         // Disk #2
242         case 2:
243                 SpawnMessage("*** DISK #2 ***");
244
245                 if (disk2EjectHovered && !floppyDrive[0].IsEmpty(1))
246                 {
247                         floppyDrive[0].EjectImage(1);
248                         SpawnMessage("*** DISK #2 EJECTED ***");
249                 }
250
251                 if (!disk2EjectHovered)
252                 {
253                         // Load the disk selector
254                         DiskSelector::ShowWindow(1);
255                 }
256
257                 break;
258         // Swap disks
259         case 3:
260                 floppyDrive[0].SwapImages();
261                 SpawnMessage("*** DISKS SWAPPED ***");
262                 break;
263         // Save state
264         case 4:
265                 SpawnMessage("*** SAVE STATE ***");
266                 break;
267         // Load state
268         case 5:
269                 SpawnMessage("*** LOAD STATE ***");
270                 break;
271         // Configuration
272         case 6:
273                 SpawnMessage("*** CONFIGURATION ***");
274                 break;
275         }
276 }
277
278
279 void GUI::MouseUp(int32_t x, int32_t y, uint32_t buttons)
280 {
281         DiskSelector::MouseUp(x, y, buttons);
282 }
283
284
285 void GUI::MouseMove(int32_t x, int32_t y, uint32_t buttons)
286 {
287         DiskSelector::MouseMove(x, y, buttons);
288
289         if (sidebarState != SBS_SHOWN)
290         {
291                 iconSelected = -1;
292
293                 if (x > SIDEBAR_X_POS)
294                 {
295 //printf("GUI: sidebar showing (x = %i)...\n", x);
296                         sidebarState = SBS_SHOWING;
297                         dx = -8;
298                 }
299                 else
300                 {
301 //printf("GUI: sidebar hiding[1] (x = %i)...\n", x);
302                         sidebarState = SBS_HIDING;
303                         dx = 8;
304                 }
305         }
306         else
307         {
308                 if (x < SIDEBAR_X_POS)
309                 {
310                         iconSelected = lastIconSelected = -1;
311                         HandleIconSelection(sdlRenderer);
312 //printf("GUI: sidebar hiding[2] (x = %i)...\n", x);
313                         sidebarState = SBS_HIDING;
314                         dx = 8;
315                 }
316                 // We're in the right zone, and the sidebar is shown, so let's select
317                 // something!
318                 else
319                 {
320                         if (y < 4 || y > 383)
321                         {
322                                 iconSelected = -1;
323                         }
324                         else
325                                 iconSelected = (y - 4) / 54;
326
327                         // It's y+2 because the sidebar sits at (SIDEBAR_X_POS, 2)
328                         disk1EjectHovered = ((x >= (SIDEBAR_X_POS + 24 + 29))
329                                 && (x < (SIDEBAR_X_POS + 24 + 29 + 8))
330                                 && (y >= (63 + 31 + 2))
331                                 && (y < (63 + 31 + 2 + 7)) ? true : false);
332
333                         disk2EjectHovered = ((x >= (SIDEBAR_X_POS + 24 + 29))
334                                 && (x < (SIDEBAR_X_POS + 24 + 29 + 8))
335                                 && (y >= (117 + 31 + 2))
336                                 && (y < (117 + 31 + 2 + 7)) ? true : false);
337
338                         if (iconSelected != lastIconSelected)
339                         {
340                                 HandleIconSelection(sdlRenderer);
341                                 lastIconSelected = iconSelected;
342
343                                 if ((iconSelected >= 0) && (iconSelected <= 6))
344                                         SpawnMessage("%s", iconHelp[iconSelected]);
345
346                                 // Show what's in the selected drive
347                                 if (iconSelected >= 1 && iconSelected <= 2)
348                                 {
349                                         if (!floppyDrive[0].IsEmpty(iconSelected - 1))
350                                                 SpawnMessage("\"%s\"", floppyDrive[0].ImageName(iconSelected - 1));
351                                 }
352                         }
353                 }
354         }
355 }
356
357
358 void GUI::HandleIconSelection(SDL_Renderer * renderer)
359 {
360         // Set up drive icons in their current states
361         AssembleDriveIcon(renderer, 0);
362         AssembleDriveIcon(renderer, 1);
363
364         // Reload the background...
365         SDL_UpdateTexture(overlay, NULL, texturePointer, 128 * sizeof(Uint32));
366
367         if (SDL_SetRenderTarget(renderer, overlay) < 0)
368         {
369                 WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
370                 return;
371         }
372
373         // Draw the icon selector, if an icon is selected
374         if (iconSelected >= 0)
375         {
376                 SDL_Rect dst;// = { 54, 54, 24 - 7, 2 };
377                 dst.w = dst.h = 54, dst.x = 24 - 7, dst.y = 2 + (iconSelected * 54);
378                 SDL_RenderCopy(renderer, iconSelection, NULL, &dst);
379         }
380
381         DrawSidebarIcons(renderer);
382
383         // Set render target back to default
384         SDL_SetRenderTarget(renderer, NULL);
385 }
386
387
388 void GUI::AssembleDriveIcon(SDL_Renderer * renderer, int driveNumber)
389 {
390         SDL_Texture * drive[2] = { disk1Icon, disk2Icon };
391         const char * number[2] = { numeralOne, numeralTwo };
392
393         if (SDL_SetRenderTarget(renderer, drive[driveNumber]) < 0)
394         {
395                 WriteLog("GUI: Could not set Render Target to overlay... (%s)\n", SDL_GetError());
396                 return;
397         }
398
399         SDL_RenderClear(renderer);
400         SDL_RenderCopy(renderer, diskIcon, NULL, NULL);
401
402         // Drive door @ (16, 7)
403         SDL_Rect dst;
404         dst.w = 8, dst.h = 10, dst.x = 16, dst.y = 7;
405         SDL_RenderCopy(renderer, (floppyDrive[0].IsEmpty(driveNumber) ?
406                 doorOpen : doorClosed), NULL, &dst);
407
408         // Numeral @ (30, 20)
409         DrawCharArray(renderer, number[driveNumber], 30, 20, 7, 7, 0xD0, 0xE0, 0xF0);
410         DrawDriveLight(renderer, driveNumber);
411         DrawEjectButton(renderer, driveNumber);
412
413         // Set render target back to default
414         SDL_SetRenderTarget(renderer, NULL);
415 }
416
417
418 void GUI::DrawEjectButton(SDL_Renderer * renderer, int driveNumber)
419 {
420         if (floppyDrive[0].IsEmpty(driveNumber))
421                 return;
422
423         uint8_t r = 0x00, g = 0xAA, b = 0x00;
424
425         if ((driveNumber == 0 && disk1EjectHovered)
426                 || (driveNumber == 1 && disk2EjectHovered))
427                 r = 0x20, g = 0xFF, b = 0x20;
428
429         DrawCharArray(renderer, ejectIcon, 29, 31, 8, 7, r, g, b);
430 }
431
432
433 void GUI::DrawDriveLight(SDL_Renderer * renderer, int driveNumber)
434 {
435         int lightState = floppyDrive[0].DriveLightStatus(driveNumber);
436         int r = 0x77, g = 0x00, b = 0x00;
437
438         if (lightState == DLS_READ)
439                 r = 0x20, g = 0xFF, b = 0x20;
440         else if (lightState == DLS_WRITE)
441                 r = 0xFF, g = 0x30, b = 0x30;
442
443         // Drive light @ (8, 21)
444         DrawCharArray(renderer, driveLight, 8, 21, 5, 5, r, g, b);
445 }
446
447
448 void GUI::DrawCharArray(SDL_Renderer * renderer, const char * array, int x,
449         int y, int w, int h, int r, int g, int b)
450 {
451         SDL_SetRenderDrawColor(renderer, r, g, b, 0xFF);
452
453         for(int j=0; j<h; j++)
454         {
455                 for(int i=0; i<w; i++)
456                 {
457                         if (array[(j * w) + i] != ' ')
458                                 SDL_RenderDrawPoint(renderer, x + i, y + j);
459                 }
460         }
461
462         SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00);
463 }
464
465
466 void GUI::HandleGUIState(void)
467 {
468         olDst.x += dx;
469
470         if (olDst.x < SIDEBAR_X_POS && sidebarState == SBS_SHOWING)
471         {
472                 olDst.x = SIDEBAR_X_POS;
473                 sidebarState = SBS_SHOWN;
474                 dx = 0;
475         }
476         else if (olDst.x > VIRTUAL_SCREEN_WIDTH && sidebarState == SBS_HIDING)
477         {
478                 olDst.x = VIRTUAL_SCREEN_WIDTH;
479                 sidebarState = SBS_HIDDEN;
480                 dx = 0;
481         }
482 }
483
484
485 void GUI::DrawSidebarIcons(SDL_Renderer * renderer)
486 {
487         SDL_Texture * icons[7] = { powerOnIcon, disk1Icon, disk2Icon, diskSwapIcon,
488                 stateSaveIcon, stateLoadIcon, configIcon };
489
490         icons[0] = (powerOnState ? powerOnIcon : powerOffIcon);
491         SDL_Rect dst = { 24, 2 + 7, 40, 40 };
492
493         for(int i=0; i<7; i++)
494         {
495                 SDL_RenderCopy(renderer, icons[i], NULL, &dst);
496                 dst.y += 54;
497         }
498 }
499
500
501 void GUI::Render(SDL_Renderer * renderer)
502 {
503         if (!overlay)
504                 return;
505
506         HandleGUIState();
507
508         if (sidebarState != SBS_HIDDEN)
509                 HandleIconSelection(renderer);
510
511         SDL_RenderCopy(renderer, overlay, NULL, &olDst);
512
513         // Hmm.
514         DiskSelector::Render(renderer);
515 }
516
517
518 /*
519 GUI Considerations:
520
521 screen is 560 x 384
522
523 cut into 7 pieces give ~54 pix per piece
524 So, let's try 40x40 icons, and see if that's good enough...
525 Selection is 54x54.
526
527 drive proportions: 1.62 : 1
528
529 Icon order:
530
531 +-----+
532 |     |
533 |Power|
534 |     |
535 +-----+
536
537 +-----+
538 |     |
539 |Disk1|
540 |    ^| <-- eject button
541 +-----+
542
543 +-----+
544 |     |
545 |Disk2|
546 |    ^|
547 +-----+
548
549 +-----+
550 |     |
551 |Swap |
552 |     |
553 +-----+
554
555 +-----+
556 |     |
557 |Confg|
558 |     |
559 +-----+
560
561 maybe state save/load
562
563 */
564