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