1// MIT License
2
3// Copyright (c) 2017 Vadim Grigoruk @nesbox // grigoruk@gmail.com
4
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23#include "surf.h"
24#include "studio/fs.h"
25#include "studio/net.h"
26#include "console.h"
27#include "menu.h"
28#include "ext/gif.h"
29#include "ext/png.h"
30
31#if defined(TIC80_PRO)
32#include "studio/project.h"
33#else
34#include "cart.h"
35#endif
36
37#include <string.h>
38
39#define MAIN_OFFSET 4
40#define MENU_HEIGHT 10
41#define ANIM 10
42#define PAGE 5
43#define COVER_WIDTH 140
44#define COVER_HEIGHT 116
45#define COVER_Y 5
46#define COVER_X (TIC80_WIDTH - COVER_WIDTH - COVER_Y)
47#define COVER_FADEIN 96
48#define COVER_FADEOUT 256
49#define CAN_OPEN_URL (__TIC_WINDOWS__ || __TIC_LINUX__ || __TIC_MACOSX__ || __TIC_ANDROID__)
50
51static const char* PngExt = PNG_EXT;
52
53typedef struct SurfItem SurfItem;
54
55struct SurfItem
56{
57 char* label;
58 char* name;
59 char* hash;
60 s32 id;
61 tic_screen* cover;
62
63 tic_palette* palette;
64
65 bool coverLoading;
66 bool dir;
67 bool project;
68};
69
70typedef struct
71{
72 SurfItem* items;
73 s32 count;
74 Surf* surf;
75 fs_done_callback done;
76 void* data;
77} AddMenuItemData;
78
79static void drawTopToolbar(Surf* surf, s32 x, s32 y)
80{
81 tic_mem* tic = surf->tic;
82
83 enum{Height = MENU_HEIGHT};
84
85 tic_api_rect(tic, x, y, TIC80_WIDTH, Height, tic_color_grey);
86 tic_api_rect(tic, x, y + Height, TIC80_WIDTH, 1, tic_color_black);
87
88 {
89 static const char Label[] = "TIC-80 SURF";
90 s32 xl = x + MAIN_OFFSET;
91 s32 yl = y + (Height - TIC_FONT_HEIGHT)/2;
92 tic_api_print(tic, Label, xl, yl+1, tic_color_black, true, 1, false);
93 tic_api_print(tic, Label, xl, yl, tic_color_white, true, 1, false);
94 }
95
96 enum{Gap = 10, TipX = 150, SelectWidth = 54};
97
98 u8 colorkey = 0;
99 tiles2ram(tic->ram, &getConfig(surf->studio)->cart->bank0.tiles);
100 tic_api_spr(tic, 12, TipX, y+1, 1, 1, &colorkey, 1, 1, tic_no_flip, tic_no_rotate);
101 {
102 static const char Label[] = "SELECT";
103 tic_api_print(tic, Label, TipX + Gap, y+3, tic_color_black, true, 1, false);
104 tic_api_print(tic, Label, TipX + Gap, y+2, tic_color_white, true, 1, false);
105 }
106
107 tic_api_spr(tic, 13, TipX + SelectWidth, y + 1, 1, 1, &colorkey, 1, 1, tic_no_flip, tic_no_rotate);
108 {
109 static const char Label[] = "BACK";
110 tic_api_print(tic, Label, TipX + Gap + SelectWidth, y +3, tic_color_black, true, 1, false);
111 tic_api_print(tic, Label, TipX + Gap + SelectWidth, y +2, tic_color_white, true, 1, false);
112 }
113}
114
115static SurfItem* getMenuItem(Surf* surf)
116{
117 return &surf->menu.items[surf->menu.pos];
118}
119
120static void drawBottomToolbar(Surf* surf, s32 x, s32 y)
121{
122 tic_mem* tic = surf->tic;
123
124 enum{Height = MENU_HEIGHT};
125
126 tic_api_rect(tic, x, y, TIC80_WIDTH, Height, tic_color_grey);
127 tic_api_rect(tic, x, y + Height, TIC80_WIDTH, 1, tic_color_black);
128 {
129 char label[TICNAME_MAX + 1];
130 char dir[TICNAME_MAX];
131 tic_fs_dir(surf->fs, dir);
132
133 sprintf(label, "/%s", dir);
134 s32 xl = x + MAIN_OFFSET;
135 s32 yl = y + (Height - TIC_FONT_HEIGHT)/2;
136 tic_api_print(tic, label, xl, yl+1, tic_color_black, true, 1, false);
137 tic_api_print(tic, label, xl, yl, tic_color_white, true, 1, false);
138 }
139
140#ifdef CAN_OPEN_URL
141
142 if(surf->menu.count > 0 && getMenuItem(surf)->hash)
143 {
144 enum{Gap = 10, TipX = 134, SelectWidth = 54};
145
146 u8 colorkey = 0;
147
148 tiles2ram(tic->ram, &getConfig(surf->studio)->cart->bank0.tiles);
149 tic_api_spr(tic, 15, TipX + SelectWidth, y + 1, 1, 1, &colorkey, 1, 1, tic_no_flip, tic_no_rotate);
150 {
151 static const char Label[] = "WEBSITE";
152 tic_api_print(tic, Label, TipX + Gap + SelectWidth, y + 3, tic_color_black, true, 1, false);
153 tic_api_print(tic, Label, TipX + Gap + SelectWidth, y + 2, tic_color_white, true, 1, false);
154 }
155 }
156#endif
157
158}
159
160static void drawMenu(Surf* surf, s32 x, s32 y)
161{
162 tic_mem* tic = surf->tic;
163
164 enum {Height = MENU_HEIGHT};
165
166 tic_api_rect(tic, 0, y + (MENU_HEIGHT - surf->anim.val.menuHeight) / 2, TIC80_WIDTH, surf->anim.val.menuHeight, tic_color_red);
167
168 s32 ym = y - surf->menu.pos * MENU_HEIGHT + (MENU_HEIGHT - TIC_FONT_HEIGHT) / 2 - surf->anim.val.pos;
169 for(s32 i = 0; i < surf->menu.count; i++, ym += Height)
170 {
171 const char* name = surf->menu.items[i].label;
172
173 if (ym > (-(TIC_FONT_HEIGHT + 1)) && ym <= TIC80_HEIGHT)
174 {
175 tic_api_print(tic, name, x + MAIN_OFFSET, ym + 1, tic_color_black, false, 1, false);
176 tic_api_print(tic, name, x + MAIN_OFFSET, ym, tic_color_white, false, 1, false);
177 }
178 }
179}
180
181static inline void cutExt(char* name, const char* ext)
182{
183 name[strlen(name)-strlen(ext)] = '\0';
184}
185
186static bool addMenuItem(const char* name, const char* title, const char* hash, s32 id, void* ptr, bool dir)
187{
188 AddMenuItemData* data = (AddMenuItemData*)ptr;
189
190 static const char CartExt[] = CART_EXT;
191
192 if(dir
193 || tic_tool_has_ext(name, CartExt)
194 || tic_tool_has_ext(name, PngExt)
195#if defined(TIC80_PRO)
196 || tic_project_ext(name)
197#endif
198 )
199 {
200 data->items = realloc(data->items, sizeof(SurfItem) * ++data->count);
201 SurfItem* item = &data->items[data->count-1];
202
203 *item = (SurfItem)
204 {
205 .name = strdup(name),
206 .hash = hash ? strdup(hash) : NULL,
207 .id = id,
208 .dir = dir,
209 };
210
211 if(dir)
212 {
213 char folder[TICNAME_MAX];
214 sprintf(folder, "[%s]", name);
215 item->label = strdup(folder);
216 }
217 else
218 {
219 item->label = title ? strdup(title) : strdup(name);
220
221 if(tic_tool_has_ext(name, CartExt))
222 cutExt(item->label, CartExt);
223 else
224 item->project = true;
225 }
226 }
227
228 return true;
229}
230
231static s32 itemcmp(const void* a, const void* b)
232{
233 const SurfItem* item1 = a;
234 const SurfItem* item2 = b;
235
236 if(item1->dir != item2->dir)
237 return item1->dir ? -1 : 1;
238 else if(item1->dir && item2->dir)
239 return strcmp(item1->name, item2->name);
240
241 return 0;
242}
243
244static void addMenuItemsDone(void* data)
245{
246 AddMenuItemData* addMenuItemData = data;
247 Surf* surf = addMenuItemData->surf;
248
249 surf->menu.items = addMenuItemData->items;
250 surf->menu.count = addMenuItemData->count;
251
252 if(!tic_fs_ispubdir(surf->fs))
253 qsort(surf->menu.items, surf->menu.count, sizeof *surf->menu.items, itemcmp);
254
255 if (addMenuItemData->done)
256 addMenuItemData->done(addMenuItemData->data);
257
258 free(addMenuItemData);
259
260 surf->loading = false;
261}
262
263static void resetMenu(Surf* surf)
264{
265 if(surf->menu.items)
266 {
267 for(s32 i = 0; i < surf->menu.count; i++)
268 {
269 SurfItem* item = &surf->menu.items[i];
270
271 free(item->name);
272
273 FREE(item->hash);
274 FREE(item->cover);
275 FREE(item->label);
276 FREE(item->palette);
277 }
278
279 free(surf->menu.items);
280
281 surf->menu.items = NULL;
282 surf->menu.count = 0;
283 }
284
285 surf->menu.pos = 0;
286}
287
288static void updateMenuItemCover(Surf* surf, s32 pos, const u8* cover, s32 size)
289{
290 SurfItem* item = &surf->menu.items[pos];
291
292 gif_image* image = gif_read_data(cover, size);
293
294 if(image)
295 {
296 item->cover = malloc(sizeof(tic_screen));
297 item->palette = malloc(sizeof(tic_palette));
298
299 if (image->width == TIC80_WIDTH
300 && image->height == TIC80_HEIGHT
301 && image->colors <= TIC_PALETTE_SIZE)
302 {
303 memcpy(item->palette, image->palette, image->colors * sizeof(tic_rgb));
304
305 for(s32 i = 0; i < TIC80_WIDTH * TIC80_HEIGHT; i++)
306 tic_tool_poke4(item->cover->data, i, image->buffer[i]);
307 }
308 else
309 {
310 memset(item->cover, 0, sizeof(tic_screen));
311 memset(item->palette, 0, sizeof(tic_palette));
312 }
313
314 gif_close(image);
315 }
316}
317
318typedef struct
319{
320 Surf* surf;
321 s32 pos;
322 char cachePath[TICNAME_MAX];
323 char dir[TICNAME_MAX];
324} CoverLoadingData;
325
326static void coverLoaded(const net_get_data* netData)
327{
328 CoverLoadingData* coverLoadingData = netData->calldata;
329 Surf* surf = coverLoadingData->surf;
330
331 if (netData->type == net_get_done)
332 {
333 tic_fs_saveroot(surf->fs, coverLoadingData->cachePath, netData->done.data, netData->done.size, false);
334
335 char dir[TICNAME_MAX];
336 tic_fs_dir(surf->fs, dir);
337
338 if(strcmp(dir, coverLoadingData->dir) == 0)
339 updateMenuItemCover(surf, coverLoadingData->pos, netData->done.data, netData->done.size);
340 }
341
342 switch (netData->type)
343 {
344 case net_get_done:
345 case net_get_error:
346 free(coverLoadingData);
347 break;
348 default: break;
349 }
350}
351
352static void requestCover(Surf* surf, SurfItem* item)
353{
354 CoverLoadingData coverLoadingData = {surf, surf->menu.pos};
355 tic_fs_dir(surf->fs, coverLoadingData.dir);
356
357 const char* hash = item->hash;
358 sprintf(coverLoadingData.cachePath, TIC_CACHE "%s.gif", hash);
359
360 {
361 s32 size = 0;
362 void* data = tic_fs_loadroot(surf->fs, coverLoadingData.cachePath, &size);
363
364 if (data)
365 {
366 updateMenuItemCover(surf, surf->menu.pos, data, size);
367 free(data);
368 }
369 }
370
371 char path[TICNAME_MAX];
372 sprintf(path, "/cart/%s/cover.gif", hash);
373
374 tic_net_get(surf->net, path, coverLoaded, MOVE(coverLoadingData));
375}
376
377static void loadCover(Surf* surf)
378{
379 tic_mem* tic = surf->tic;
380
381 SurfItem* item = getMenuItem(surf);
382
383 if(item->coverLoading)
384 return;
385
386 item->coverLoading = true;
387
388 if(!tic_fs_ispubdir(surf->fs))
389 {
390
391 s32 size = 0;
392 void* data = tic_fs_load(surf->fs, item->name, &size);
393
394 if(data)
395 {
396 tic_cartridge* cart = (tic_cartridge*)malloc(sizeof(tic_cartridge));
397
398 if(cart)
399 {
400
401 if(tic_tool_has_ext(item->name, PngExt))
402 {
403 tic_cartridge* pngcart = loadPngCart((png_buffer){data, size});
404
405 if(pngcart)
406 {
407 memcpy(cart, pngcart, sizeof(tic_cartridge));
408 free(pngcart);
409 }
410 else memset(cart, 0, sizeof(tic_cartridge));
411 }
412#if defined(TIC80_PRO)
413 else if(tic_project_ext(item->name))
414 tic_project_load(item->name, data, size, cart);
415#endif
416 else
417 tic_cart_load(cart, data, size);
418
419 if(!EMPTY(cart->bank0.screen.data) && !EMPTY(cart->bank0.palette.vbank0.data))
420 {
421 memcpy((item->palette = malloc(sizeof(tic_palette))), &cart->bank0.palette.vbank0, sizeof(tic_palette));
422 memcpy((item->cover = malloc(sizeof(tic_screen))), &cart->bank0.screen, sizeof(tic_screen));
423 }
424
425 free(cart);
426 }
427
428 free(data);
429 }
430 }
431 else if(item->hash && !item->cover)
432 {
433 requestCover(surf, item);
434 }
435}
436
437static void initItemsAsync(Surf* surf, fs_done_callback callback, void* calldata)
438{
439 resetMenu(surf);
440
441 surf->loading = true;
442
443 char dir[TICNAME_MAX];
444 tic_fs_dir(surf->fs, dir);
445
446 AddMenuItemData data = { NULL, 0, surf, callback, calldata};
447
448 if(strcmp(dir, "") != 0)
449 addMenuItem("..", NULL, NULL, 0, &data, true);
450
451 tic_fs_enum(surf->fs, addMenuItem, addMenuItemsDone, MOVE(data));
452}
453
454typedef struct
455{
456 Surf* surf;
457 char* last;
458} GoBackDirDoneData;
459
460static void onGoBackDirDone(void* data)
461{
462 GoBackDirDoneData* goBackDirDoneData = data;
463 Surf* surf = goBackDirDoneData->surf;
464
465 char current[TICNAME_MAX];
466 tic_fs_dir(surf->fs, current);
467
468 for(s32 i = 0; i < surf->menu.count; i++)
469 {
470 const SurfItem* item = &surf->menu.items[i];
471
472 if(item->dir)
473 {
474 char path[TICNAME_MAX];
475
476 if(strlen(current))
477 sprintf(path, "%s/%s", current, item->name);
478 else strcpy(path, item->name);
479
480 if(strcmp(path, goBackDirDoneData->last) == 0)
481 {
482 surf->menu.pos = i;
483 break;
484 }
485 }
486 }
487
488 free(goBackDirDoneData->last);
489 free(goBackDirDoneData);
490
491 surf->anim.movie = resetMovie(&surf->anim.goback.show);
492}
493
494static void onGoBackDir(void* data)
495{
496 Surf* surf = data;
497 char last[TICNAME_MAX];
498 tic_fs_dir(surf->fs, last);
499
500 tic_fs_dirback(surf->fs);
501
502 GoBackDirDoneData goBackDirDoneData = {surf, strdup(last)};
503 initItemsAsync(surf, onGoBackDirDone, MOVE(goBackDirDoneData));
504}
505
506static void onGoToDirDone(void* data)
507{
508 Surf* surf = data;
509 surf->anim.movie = resetMovie(&surf->anim.gotodir.show);
510}
511
512static void onGoToDir(void* data)
513{
514 Surf* surf = data;
515 SurfItem* item = getMenuItem(surf);
516
517 tic_fs_changedir(surf->fs, item->name);
518 initItemsAsync(surf, onGoToDirDone, surf);
519}
520
521static void goBackDir(Surf* surf)
522{
523 char dir[TICNAME_MAX];
524 tic_fs_dir(surf->fs, dir);
525
526 if(strcmp(dir, "") != 0)
527 {
528 playSystemSfx(surf->studio, 2);
529
530 surf->anim.movie = resetMovie(&surf->anim.goback.hide);
531 }
532}
533
534static void changeDirectory(Surf* surf, const char* name)
535{
536 if (strcmp(name, "..") == 0)
537 {
538 goBackDir(surf);
539 }
540 else
541 {
542 playSystemSfx(surf->studio, 2);
543 surf->anim.movie = resetMovie(&surf->anim.gotodir.hide);
544 }
545}
546
547static void onCartLoaded(void* data)
548{
549 Surf* surf = data;
550 runGame(surf->studio);
551}
552
553static void onLoadCommandConfirmed(Studio* studio, bool yes, void* data)
554{
555 if(yes)
556 {
557 Surf* surf = data;
558 SurfItem* item = getMenuItem(surf);
559
560 if (item->hash)
561 {
562 surf->console->loadByHash(surf->console, item->name, item->hash, NULL, onCartLoaded, surf);
563 }
564 else
565 {
566 surf->console->load(surf->console, item->name);
567 runGame(surf->studio);
568 }
569 }
570}
571
572static void onPlayCart(void* data)
573{
574 Surf* surf = data;
575 SurfItem* item = getMenuItem(surf);
576
577 studioCartChanged(surf->studio)
578 ? confirmLoadCart(surf->studio, onLoadCommandConfirmed, surf)
579 : onLoadCommandConfirmed(surf->studio, true, surf);
580}
581
582static void loadCart(Surf* surf)
583{
584 SurfItem* item = getMenuItem(surf);
585
586 if(tic_tool_has_ext(item->name, PngExt))
587 {
588 s32 size = 0;
589 void* data = tic_fs_load(surf->fs, item->name, &size);
590
591 if(data)
592 {
593 tic_cartridge* cart = loadPngCart((png_buffer){data, size});
594
595 if(cart)
596 {
597 surf->anim.movie = resetMovie(&surf->anim.play);
598 free(cart);
599 }
600 }
601 }
602 else surf->anim.movie = resetMovie(&surf->anim.play);
603}
604
605static void move(Surf* surf, s32 dir)
606{
607 surf->menu.target = (surf->menu.pos + surf->menu.count + dir) % surf->menu.count;
608
609 Anim* anim = surf->anim.move.items;
610 anim->end = (surf->menu.target - surf->menu.pos) * MENU_HEIGHT;
611
612 surf->anim.movie = resetMovie(&surf->anim.move);
613}
614
615static void processGamepad(Surf* surf)
616{
617 tic_mem* tic = surf->tic;
618
619 enum{Frames = MENU_HEIGHT};
620
621 {
622 enum{Hold = KEYBOARD_HOLD, Period = Frames};
623
624 enum
625 {
626 Up, Down, Left, Right, A, B, X, Y
627 };
628
629 if(tic_api_btnp(tic, Up, Hold, Period)
630 || tic_api_keyp(tic, tic_key_up, Hold, Period))
631 {
632 move(surf, -1);
633 playSystemSfx(surf->studio, 2);
634 }
635 else if(tic_api_btnp(tic, Down, Hold, Period)
636 || tic_api_keyp(tic, tic_key_down, Hold, Period))
637 {
638 move(surf, +1);
639 playSystemSfx(surf->studio, 2);
640 }
641 else if(tic_api_btnp(tic, Left, Hold, Period)
642 || tic_api_keyp(tic, tic_key_left, Hold, Period)
643 || tic_api_keyp(tic, tic_key_pageup, Hold, Period))
644 {
645 s32 dir = -PAGE;
646
647 if(surf->menu.pos == 0) dir = -1;
648 else if(surf->menu.pos <= PAGE) dir = -surf->menu.pos;
649
650 move(surf, dir);
651 }
652 else if(tic_api_btnp(tic, Right, Hold, Period)
653 || tic_api_keyp(tic, tic_key_right, Hold, Period)
654 || tic_api_keyp(tic, tic_key_pagedown, Hold, Period))
655 {
656 s32 dir = +PAGE, last = surf->menu.count - 1;
657
658 if(surf->menu.pos == last) dir = +1;
659 else if(surf->menu.pos + PAGE >= last) dir = last - surf->menu.pos;
660
661 move(surf, dir);
662 }
663
664 if(tic_api_btnp(tic, A, -1, -1)
665 || tic_api_keyp(tic, tic_key_return, -1, -1))
666 {
667 SurfItem* item = getMenuItem(surf);
668 item->dir
669 ? changeDirectory(surf, item->name)
670 : loadCart(surf);
671 }
672
673 if(tic_api_btnp(tic, B, -1, -1)
674 || tic_api_keyp(tic, tic_key_backspace, -1, -1))
675 {
676 goBackDir(surf);
677 }
678
679#ifdef CAN_OPEN_URL
680
681 if(tic_api_btnp(tic, Y, -1, -1))
682 {
683 SurfItem* item = getMenuItem(surf);
684
685 if(!item->dir)
686 {
687 char url[TICNAME_MAX];
688 sprintf(url, TIC_WEBSITE "/play?cart=%i", item->id);
689 tic_sys_open_url(url);
690 }
691 }
692#endif
693
694 }
695
696}
697
698static inline bool isIdle(Surf* surf)
699{
700 return surf->anim.movie == &surf->anim.idle;
701}
702
703static void tick(Surf* surf)
704{
705 processAnim(surf->anim.movie, surf);
706
707 if(!surf->init)
708 {
709 initItemsAsync(surf, NULL, NULL);
710 surf->anim.movie = resetMovie(&surf->anim.show);
711 surf->init = true;
712 }
713
714 tic_mem* tic = surf->tic;
715 tic_api_cls(tic, TIC_COLOR_BG);
716
717 studio_menu_anim(surf->tic, surf->ticks++);
718
719 if (isIdle(surf) && surf->menu.count > 0)
720 {
721 processGamepad(surf);
722 if(tic_api_keyp(tic, tic_key_escape, -1, -1))
723 setStudioMode(surf->studio, TIC_CONSOLE_MODE);
724 }
725
726 if (getStudioMode(surf->studio) != TIC_SURF_MODE) return;
727
728 if (surf->menu.count > 0)
729 {
730 loadCover(surf);
731
732 tic_screen* cover = getMenuItem(surf)->cover;
733
734 if(cover)
735 memcpy(tic->ram->vram.screen.data, cover->data, sizeof(tic_screen));
736 }
737
738 VBANK(tic, 1)
739 {
740 tic_api_cls(tic, tic->ram->vram.vars.clear = tic_color_yellow);
741 memcpy(tic->ram->vram.palette.data, getConfig(surf->studio)->cart->bank0.palette.vbank0.data, sizeof(tic_palette));
742
743 if(surf->menu.count > 0)
744 {
745 drawMenu(surf, surf->anim.val.menuX, (TIC80_HEIGHT - MENU_HEIGHT)/2);
746 }
747 else if(!surf->loading)
748 {
749 static const char Label[] = "You don't have any files...";
750 s32 size = tic_api_print(tic, Label, 0, -TIC_FONT_HEIGHT, tic_color_white, true, 1, false);
751 tic_api_print(tic, Label, (TIC80_WIDTH - size) / 2, (TIC80_HEIGHT - TIC_FONT_HEIGHT)/2, tic_color_white, true, 1, false);
752 }
753
754 drawTopToolbar(surf, 0, surf->anim.val.topBarY - MENU_HEIGHT);
755 drawBottomToolbar(surf, 0, TIC80_HEIGHT - surf->anim.val.bottomBarY);
756 }
757}
758
759static void resume(Surf* surf)
760{
761 surf->anim.movie = resetMovie(&surf->anim.show);
762}
763
764static void scanline(tic_mem* tic, s32 row, void* data)
765{
766 Surf* surf = (Surf*)data;
767
768 if(surf->menu.count > 0)
769 {
770 const SurfItem* item = getMenuItem(surf);
771
772 if(item->palette)
773 {
774 if(row == 0)
775 {
776 memcpy(&tic->ram->vram.palette, item->palette, sizeof(tic_palette));
777 fadePalette(&tic->ram->vram.palette, surf->anim.val.coverFade);
778 }
779
780 return;
781 }
782 }
783
784 studio_menu_anim_scanline(tic, row, NULL);
785}
786
787static void emptyDone(void* data) {}
788
789static void setIdle(void* data)
790{
791 Surf* surf = data;
792 surf->anim.movie = resetMovie(&surf->anim.idle);
793}
794
795static void setLeftShow(void* data)
796{
797 Surf* surf = data;
798 surf->anim.movie = resetMovie(&surf->anim.gotodir.show);
799}
800
801static void freeAnim(Surf* surf)
802{
803 FREE(surf->anim.show.items);
804 FREE(surf->anim.play.items);
805 FREE(surf->anim.move.items);
806 FREE(surf->anim.gotodir.show.items);
807 FREE(surf->anim.gotodir.hide.items);
808 FREE(surf->anim.goback.show.items);
809 FREE(surf->anim.goback.hide.items);
810}
811
812static void moveDone(void* data)
813{
814 Surf* surf = data;
815 surf->menu.pos = surf->menu.target;
816 surf->anim.val.pos = 0;
817 surf->anim.movie = resetMovie(&surf->anim.idle);
818}
819
820void initSurf(Surf* surf, Studio* studio, struct Console* console)
821{
822 freeAnim(surf);
823
824 *surf = (Surf)
825 {
826 .studio = studio,
827 .tic = getMemory(studio),
828 .console = console,
829 .fs = console->fs,
830 .net = console->net,
831 .tick = tick,
832 .ticks = 0,
833 .init = false,
834 .loading = true,
835 .resume = resume,
836 .menu =
837 {
838 .pos = 0,
839 .items = NULL,
840 .count = 0,
841 },
842 .anim =
843 {
844 .idle = {.done = emptyDone,},
845
846 .show = MOVIE_DEF(ANIM, setIdle,
847 {
848 {0, MENU_HEIGHT, ANIM, &surf->anim.val.topBarY, AnimEaseIn},
849 {0, MENU_HEIGHT, ANIM, &surf->anim.val.bottomBarY, AnimEaseIn},
850 {-TIC80_WIDTH, 0, ANIM, &surf->anim.val.menuX, AnimEaseIn},
851 {0, MENU_HEIGHT, ANIM, &surf->anim.val.menuHeight, AnimEaseIn},
852 {COVER_FADEOUT, COVER_FADEIN, ANIM, &surf->anim.val.coverFade, AnimEaseIn},
853 }),
854
855 .play = MOVIE_DEF(ANIM, onPlayCart,
856 {
857 {MENU_HEIGHT, 0, ANIM, &surf->anim.val.topBarY, AnimEaseIn},
858 {MENU_HEIGHT, 0, ANIM, &surf->anim.val.bottomBarY, AnimEaseIn},
859 {0, -TIC80_WIDTH, ANIM, &surf->anim.val.menuX, AnimEaseIn},
860 {MENU_HEIGHT, 0, ANIM, &surf->anim.val.menuHeight, AnimEaseIn},
861 {COVER_FADEIN, COVER_FADEOUT, ANIM, &surf->anim.val.coverFade, AnimEaseIn},
862 }),
863
864 .move = MOVIE_DEF(9, moveDone, {{0, 0, 9, &surf->anim.val.pos, AnimLinear}}),
865
866 .gotodir =
867 {
868 .show = MOVIE_DEF(ANIM, setIdle,
869 {
870 {TIC80_WIDTH, 0, ANIM, &surf->anim.val.menuX, AnimEaseIn},
871 {0, MENU_HEIGHT, ANIM, &surf->anim.val.menuHeight, AnimEaseIn},
872 }),
873
874 .hide = MOVIE_DEF(ANIM, onGoToDir,
875 {
876 {0, -TIC80_WIDTH, ANIM, &surf->anim.val.menuX, AnimEaseIn},
877 {MENU_HEIGHT, 0, ANIM, &surf->anim.val.menuHeight, AnimEaseIn},
878 }),
879 },
880
881 .goback =
882 {
883 .show = MOVIE_DEF(ANIM, setIdle,
884 {
885 {-TIC80_WIDTH, 0, ANIM, &surf->anim.val.menuX, AnimEaseIn},
886 {0, MENU_HEIGHT, ANIM, &surf->anim.val.menuHeight, AnimEaseIn},
887 }),
888
889 .hide = MOVIE_DEF(ANIM, onGoBackDir,
890 {
891 {0, TIC80_WIDTH, ANIM, &surf->anim.val.menuX, AnimEaseIn},
892 {MENU_HEIGHT, 0, ANIM, &surf->anim.val.menuHeight, AnimEaseIn},
893 }),
894 },
895 },
896 .scanline = scanline,
897 };
898
899 surf->anim.movie = resetMovie(&surf->anim.idle);
900
901 tic_fs_makedir(surf->fs, TIC_CACHE);
902}
903
904void freeSurf(Surf* surf)
905{
906 freeAnim(surf);
907 resetMenu(surf);
908 free(surf);
909}