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