cepheid

An Aurora 4X clone
Log | Files | Refs | README

commit 8d0163a8ba5dfc998b7a73ae8b8c938ef3d683fa
parent 587eaf48e684bb58e21a9593a7361c97a8454cdb
Author: hhvn <dev@hhvn.uk>
Date:   Sun, 20 Nov 2022 21:51:28 +0000

Start menu

Diffstat:
M.gitignore | 1+
Adata/icons/burger.png | 0
Adata/icons/burger.xcf | 0
Adata/splash.png | 0
Adata/splash.png.source | 1+
Msrc/data.c | 8++++++++
Msrc/main.c | 29++++++++++++++++-------------
Msrc/main.h | 13++++++++++++-
Msrc/maths.h | 3+++
Msrc/save.c | 11+++++++----
Msrc/str.c | 13+++++++++++++
Msrc/struct.h | 3+++
Msrc/system.c | 2+-
Msrc/ui.c | 22+++++++++++++++++++---
Msrc/views/main.c | 4----
Asrc/views/smenu.c | 359+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/views/struct.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
17 files changed, 493 insertions(+), 26 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,4 +1,5 @@ *.o +data/*.o data/*.h data/*.c data/sol diff --git a/data/icons/burger.png b/data/icons/burger.png Binary files differ. diff --git a/data/icons/burger.xcf b/data/icons/burger.xcf Binary files differ. diff --git a/data/splash.png b/data/splash.png Binary files differ. diff --git a/data/splash.png.source b/data/splash.png.source @@ -0,0 +1 @@ +Source: NASA, public domain diff --git a/src/data.c b/src/data.c @@ -9,6 +9,8 @@ #include "../data/icons/design.h" #include "../data/icons/sys.h" #include "../data/icons/settings.h" +#include "../data/icons/burger.h" +#include "../data/splash.h" #define IMAGE(name) \ static Image raw_image_##name; \ @@ -22,6 +24,8 @@ IMAGE(fleet); IMAGE(design); IMAGE(sys); IMAGE(settings); +IMAGE(burger); +IMAGE(splash); #define IMAGE_LOAD(name) \ raw_image_##name = LoadImageFromMemory(".png", \ @@ -42,6 +46,8 @@ data_load(void) { IMAGE_LOAD(design); IMAGE_LOAD(sys); IMAGE_LOAD(settings); + IMAGE_LOAD(burger); + IMAGE_LOAD(splash); } #define IMAGE_UNLOAD(name) \ @@ -57,4 +63,6 @@ data_unload(void) { IMAGE_UNLOAD(design); IMAGE_UNLOAD(sys); IMAGE_UNLOAD(settings); + IMAGE_UNLOAD(burger); + IMAGE_UNLOAD(splash); } diff --git a/src/main.c b/src/main.c @@ -10,6 +10,8 @@ Save *save = NULL; int sigint = 0; int sigterm = 0; +int quit = 0; +int view_before_smenu = UI_VIEW_MAIN; void error(int code, char *fmt, ...) { @@ -64,10 +66,6 @@ main(void) { data_load(); - if (!save_exists(DEFSAVE)) - save_create(DEFSAVE); - save_read(DEFSAVE); - /* The window is hidden so that only the loading bar is shown. Hiding * and unhiding the window also has the added effect of making it * behave like a normal window in window managers, rather than having @@ -76,14 +74,17 @@ main(void) { ui_update_screen(); while (ui_loop()) { - if (IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT)) { + if (IsKeyPressed(KEY_ESCAPE)) + view_tabs.sel = UI_VIEW_SMENU; + + if (save && (IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT))) { /* AAAAAAAAAAHHHHHHHHHHHH. WHY NOT JUST USE KEY_1, KEY_2..! */ - if (IsKeyPressed(KEY_ONE)) view_tabs.sel = 0; - else if (IsKeyPressed(KEY_TWO)) view_tabs.sel = 1; - else if (IsKeyPressed(KEY_THREE)) view_tabs.sel = 2; - else if (IsKeyPressed(KEY_FOUR)) view_tabs.sel = 3; - else if (IsKeyPressed(KEY_FIVE)) view_tabs.sel = 4; - else if (IsKeyPressed(KEY_SIX)) view_tabs.sel = 5; + if (IsKeyPressed(KEY_ONE)) view_tabs.sel = 1; + else if (IsKeyPressed(KEY_TWO)) view_tabs.sel = 2; + else if (IsKeyPressed(KEY_THREE)) view_tabs.sel = 3; + else if (IsKeyPressed(KEY_FOUR)) view_tabs.sel = 4; + else if (IsKeyPressed(KEY_FIVE)) view_tabs.sel = 5; + else if (IsKeyPressed(KEY_SIX)) view_tabs.sel = 6; } view_handlers[view_tabs.sel](view_prev != view_tabs.sel); @@ -91,7 +92,10 @@ main(void) { BeginDrawing(); ClearBackground(col_bg); view_drawers[view_tabs.sel](); - ui_draw_views(); + if (view_tabs.sel != UI_VIEW_SMENU) { + ui_draw_views(); + view_before_smenu = view_tabs.sel; + } EndDrawing(); view_prev = view_tabs.sel; @@ -99,7 +103,6 @@ main(void) { data_unload(); ui_deinit(); - save_write(); dbcleanup(); return 0; } diff --git a/src/main.h b/src/main.h @@ -20,6 +20,8 @@ extern Save *save; extern int sigint; extern int sigterm; +extern int quit; +extern int view_before_smenu; void error(int code, char *fmt, ...); void warning(char *fmt, ...); @@ -34,6 +36,7 @@ char * smprintf(char *fmt, ...); /* return allocated string */ char * nstrdup(char *str); /* NULL-safe */ char * strkm(float km); char * strly(float km); +char * strdate(time_t time); int streq(char *s1, char *s2); /* NULL-safe, no `== 0` required */ int strprefix(char *str, char *prefix); char * strsuffix(char *str, char *suffix); @@ -111,6 +114,7 @@ void ui_draw_border(int x, int y, int w, int h, int px); void ui_draw_border_around(int x, int y, int w, int h, int px); void ui_draw_ring(int x, int y, float r, Color col); void ui_draw_texture(Texture2D texture, int x, int y); +void ui_draw_texture_part(Texture2D texture, int x, int y, int fx, int fy, int w, int h); void ui_draw_circle(int x, int y, float r, Color col); void ui_draw_line(int sx, int sy, int ex, int ey, float thick, Color col); void ui_draw_line_v(Vector start, Vector end, float thick, Color col); @@ -154,6 +158,12 @@ extern View_sys view_sys; void ui_handle_view_sys(int nowsel); void ui_draw_view_sys(void); +/* views/smenu.c */ +extern View_smenu view_smenu; +void ui_handle_view_smenu(int nowsel); +void ui_draw_view_smenu(void); +void checkbeforequit(void); + /* pane.c */ void pane_begin(Pane *f); void pane_end(void); @@ -198,7 +208,8 @@ extern Texture image_fleet; extern Texture image_design; extern Texture image_sys; extern Texture image_settings; -#define DATA_LOAD_STEPS (1 + 7) +extern Texture image_burger; +extern Texture image_splash; void data_load(void); void data_unload(void); diff --git a/src/maths.h b/src/maths.h @@ -6,6 +6,9 @@ #define GIGA (MEGA * 1000) #define MEGA (KILO * 1000) #define KILO 1000 +#define MILLI (1.0 / 1000.0) +#define MICRO (MILLI / 1000.0) +#define NANO (MICRO / 1000.0) #define C_MS 299792458 /* c, in m/s */ diff --git a/src/save.c b/src/save.c @@ -19,7 +19,7 @@ save_free(void) { void save_read(char *name) { char dir[PATH_MAX]; - /* char *str; */ + char *str; if (save) save_free(); @@ -36,10 +36,13 @@ save_read(char *name) { save->db.races = smprintf("%s/Races", dir); save->db.systems = smprintf("%s/Systems", dir); save->db.fleets = smprintf("%s/Fleets", dir); - /* if ((str = dbget(save->db.dir, "index", "homesystem"))) */ - /* save->homesys = sys_get(str); */ - save->homesys = NULL; sys_tree_load(); + + if ((str = dbget(save->db.dir, "index", "homesystem"))) + save->homesys = sys_get(str); + + view_main.sys = NULL; + view_main.sys = sys_default(); return; }; diff --git a/src/str.c b/src/str.c @@ -6,6 +6,7 @@ #include <wchar.h> #include <errno.h> #include <math.h> +#include <time.h> #include "main.h" #define TEXTBUF 2048 @@ -132,6 +133,18 @@ strly(float km) { return sfprintf("%.2f ls", ls); } +char * +strdate(time_t time) { + struct tm *tm; + char *ret = falloc(256); + + tm = localtime(&time); + + strftime(ret, 256, "%c", tm); + + return ret; +} + int streq(char *s1, char *s2) { if (s1 == s2) diff --git a/src/struct.h b/src/struct.h @@ -24,6 +24,7 @@ typedef void (*Treesetter)(char *dir, char *group, char *name, int depth, Tree * typedef void (*Treefree)(Tree *t); typedef int (*Treefilter)(Tree *t, void *data); typedef void (*Treeprinter)(int x, int y, Treeview *tv, Tree *t); +typedef void (*Treedclick)(Treeview *tv); typedef int (*Treecompar)(Tree *a, Tree *b, void *data); @@ -219,6 +220,7 @@ struct Treeview { void *fdata; /* data to pass to filter() */ Treefilter filter; /* hide nodes when 0 is returned */ Treeprinter print; /* prints data related to the node */ + Treedclick dclick; /* runs on double click of selected node */ /* internal */ Geom rect; Pane pane; @@ -240,6 +242,7 @@ typedef struct { } Focus; enum UiViews { + UI_VIEW_SMENU, UI_VIEW_MAIN, UI_VIEW_COLONIES, UI_VIEW_BODIES, diff --git a/src/system.c b/src/system.c @@ -266,5 +266,5 @@ sys_default(void) { else if (save->systems.d && save->systems.d->data) return save->systems.d->data; else - exit(1); + error(1, "could not locate any systems\n"); } diff --git a/src/ui.c b/src/ui.c @@ -7,6 +7,7 @@ #include "main.h" void (*view_handlers[UI_VIEW_LAST])(int) = { + [UI_VIEW_SMENU] = ui_handle_view_smenu, [UI_VIEW_MAIN] = ui_handle_view_main, [UI_VIEW_COLONIES] = ui_handle_view_colonies, [UI_VIEW_BODIES] = ui_handle_view_bodies, @@ -17,6 +18,7 @@ void (*view_handlers[UI_VIEW_LAST])(int) = { }; void (*view_drawers[UI_VIEW_LAST])(void) = { + [UI_VIEW_SMENU] = ui_draw_view_smenu, [UI_VIEW_MAIN] = ui_draw_view_main, [UI_VIEW_COLONIES] = ui_draw_view_colonies, [UI_VIEW_BODIES] = ui_draw_view_bodies, @@ -33,13 +35,16 @@ Mouse mouse = { 0 }; Tabs view_tabs = { /* Tactical is the terminology used in Aurora, so I decided to use it * in the ui; in the code it's just called 'main' for my fingers' sake */ - UI_VIEW_LAST, 0, {{&image_tactical, "Tactical", 0}, + UI_VIEW_LAST, 0, { + {&image_burger, NULL, VIEWS_HEIGHT}, + {&image_tactical, "Tactical", 0}, {&image_colonies, "Colonies", 0}, {&image_bodies, "Bodies", 0}, {&image_fleet, "Fleets", 0}, {&image_design, "Design", 0}, {&image_sys, "Systems", 0}, - {&image_settings, "Settings", 0}} + {&image_settings, "Settings", 0}, + } }; int charpx; /* thank god for monospaced fonts */ @@ -48,8 +53,8 @@ void ui_init(void) { SetWindowState(FLAG_WINDOW_RESIZABLE|FLAG_WINDOW_HIDDEN); SetTargetFPS(TARGET_FPS); - SetExitKey(KEY_NULL); InitWindow(500, 500, ""); + SetExitKey(KEY_NULL); } void @@ -70,6 +75,9 @@ ui_focus(enum GuiElements type, void *p) { int ui_loop(void) { if (WindowShouldClose() || sigint || sigterm) + checkbeforequit(); + + if (((sigint || sigterm) && view_smenu.save.check) || quit) return 0; ffree(); @@ -142,6 +150,7 @@ ui_title(char *fmt, ...) { int ui_textsize(char *text) { + if (!text) return 0; return charpx * strlen(text); } @@ -293,6 +302,13 @@ ui_draw_texture(Texture2D texture, int x, int y) { } void +ui_draw_texture_part(Texture2D texture, int x, int y, int fx, int fy, int w, int h) { + if (!pane_visible(y, y + h)) + return; + DrawTextureRec(texture, (Rectangle){fx, fy, w, h}, (Vector){x, y}, NO_TINT); +} + +void ui_draw_circle(int x, int y, float r, Color col) { if (!pane_visible(y - r, y + r)) return; diff --git a/src/views/main.c b/src/views/main.c @@ -107,10 +107,6 @@ ui_handle_view_main(int nowsel) { } } - if (!view_main.sys) { - view_main.sys = sys_default(); - } - if (nowsel) ui_title("Tactical: %s", view_main.sys->name); diff --git a/src/views/smenu.c b/src/views/smenu.c @@ -0,0 +1,359 @@ +#include <raylib.h> +#include <dirent.h> +#include <string.h> +#include <stdlib.h> +#include <limits.h> +#include <stdio.h> +#include <time.h> +#include <sys/stat.h> +#include "../main.h" + +#define CONTCUR "Back to current game" + +#define LOAD_W 300 +#define LOAD_H 350 +#define LOAD_NAMEW 100 +#define BUTTON_W 50 + +static void savecheck(char *msg, void (*action)(void)); +static void savecheck_callback(int arg); +static void buttonhandler(int arg); +static void newhandler(void); +static int newhandler_actual(Input *in); +static void newhandler_back(int arg); +static void quithandler(void); +static void loadhandler(void); +static void loadhandler_actual(void); +static void loadhandler_click(Treeview *tv); +static void loadhandler_button(int arg); +static void loadprinter(int x, int y, Treeview *tv, Tree *t); +static void loadadd(char *dir, time_t mod); +static void loadinit(char *sdir); + +static View_smenu *v = &view_smenu; +View_smenu view_smenu = { + .main = { + .x = PAD, + .y = PAD, + .w = 250, + .h = 0, + }, + .b = { + [SMENU_NEW] = {1, "New Game", buttonhandler, SMENU_NEW }, + [SMENU_CONT] = {0, view_smenu.cont.label, buttonhandler, SMENU_CONT }, + [SMENU_SAVE] = {0, "Save Game", buttonhandler, SMENU_SAVE }, + [SMENU_LOAD] = {0, "Load Game", buttonhandler, SMENU_LOAD }, + [SMENU_QUIT] = {1, "Quit", buttonhandler, SMENU_QUIT }, + }, + .new = { + .disp = 0, + .name = { + .placeholder = "Save name", + .onenter = newhandler_actual, + }, + .create = {1, "Create", NULL, 0, .submit = &view_smenu.new.name}, + .back = {1, "Back", newhandler_back, 0}, + }, + .cont = { + .save = NULL, + .label = "Continue", + }, + .save = { + .check = 0, + .msg = NULL, + .back = {1, "Back", NULL, -1}, + .save = {1, "Save", NULL, 1}, + .discard = {1, "Discard", NULL, 0}, + }, + .load = { + .init = 1, + .disp = 0, + .saves = {0}, + .savelist = { + .t = &view_smenu.load.saves, + .sel = NULL, + .selmask = 0xff, + .colmask = 0x00, + .filter = NULL, + .print = loadprinter, + .dclick = loadhandler_click, + }, + .load = {0, "Load", loadhandler_button, 0 }, + } +}; + +static void +savecheck(char *msg, void (*action)(void)) { + v->save.check = 1; + v->save.back.func = + v->save.save.func = + v->save.discard.func = savecheck_callback; + v->save.msg = msg; + v->save.func = action; +} + +static void +savecheck_callback(int arg) { + v->save.check = 0; + if (arg) + save_write(); + if (arg != -1) + v->save.func(); +} + + +static void +buttonhandler(int arg) { + switch (arg) { + case SMENU_NEW: + if (save && save_changed()) + savecheck("The current game hasn't been saved. Save it?", newhandler); + else + newhandler(); + break; + case SMENU_SAVE: + save_write(); + /* add to event log */ + /* fallthrough */ + case SMENU_CONT: + if (!save) { + save_read(v->cont.save->name); + strcpy(v->cont.label, CONTCUR); + } + view_tabs.sel = view_before_smenu; + break; + case SMENU_LOAD: + if (save && save_changed()) + savecheck("The current game hasn't been saved. Save it?", loadhandler); + else + loadhandler(); + break; + case SMENU_QUIT: + checkbeforequit(); + break; + } +} + +static void +newhandler(void) { + v->new.disp = 1; +} + +static int +newhandler_actual(Input *in) { + save_create(v->new.name.str); + save_read(v->new.name.str); + loadadd(v->new.name.str, time(NULL)); + view_tabs.sel = UI_VIEW_MAIN; + v->new.disp = 0; + return 1; +} + +static void +newhandler_back(int arg) { + gui_input_clear(&v->new.name); + v->new.disp = 0; +} + +static void +quithandler(void) { + quit = 1; +} + +static void +loadhandler(void) { + v->load.disp = 1; + /* strcpy(v->cont.label, CONTCUR); */ + /* v->b[SMENU_CONT].enabled = 1; */ +} + +static void +loadhandler_actual(void) { + struct Loadable *l; + + l = v->load.savelist.sel->data; + save_read(l->name); + view_tabs.sel = UI_VIEW_MAIN; + v->load.disp = 0; + return; +} + +static void +loadhandler_click(Treeview *tv) { + loadhandler_actual(); +} + +static void +loadhandler_button(int arg) { + loadhandler_actual(); +} + +static void +loadprinter(int x, int y, Treeview *tv, Tree *t) { + struct Loadable *l; + Color c = (tv->sel == t) ? col_info : col_fg; + + if (!t) { + ui_print(x + PAD, y, col_fg, "Name"); + ui_print(x + PAD + LOAD_NAMEW, y, col_fg, "Last saved"); + } else { + l = t->data; + ui_printw(x, y, LOAD_NAMEW, c, "%s", l->name); + ui_print( x + LOAD_NAMEW, y, c, "%s", strdate(l->mod)); + } +} + +static void +loadadd(char *dir, time_t mod) { + struct Loadable *p; + + p = malloc(sizeof(struct Loadable)); + p->name = nstrdup(dir); + p->mod = mod; + + tree_add_child(&v->load.saves, p->name, 1, p, NULL); +} + +void +checkbeforequit(void) { + if (save_changed()) { + view_tabs.sel = UI_VIEW_SMENU; + savecheck("There are unsaved changes. Save before quitting?", quithandler); + } else { + quithandler(); + } +} + +static void +loadinit(char *sdir) { + struct dirent **dirent; + struct stat st; + char path[PATH_MAX]; + int n, i; + + n = scandir(sdir, &dirent, NULL, alphasort); + if (n < 0) return; + + for (i = 0; i < n; i++) { + snprintf(path, sizeof(path), "%s/%s", sdir, dirent[i]->d_name); + stat(path, &st); + if (dirent[i]->d_name[0] != '.' && S_ISDIR(st.st_mode)) + loadadd(dirent[i]->d_name, st.st_mtime); + free(dirent[i]); + } + free(dirent); +} + +void +ui_handle_view_smenu(int nowsel) { + Tree *t; + struct Loadable *cont, *l; + + v->main.h = PAD + SMENU_LAST * (BUTTON_HEIGHT + PAD); + v->main.x = (screen.w - v->main.w) / 2; + v->main.y = (screen.h - v->main.h) / 2; + + if (v->load.init) { + loadinit(SAVEDIR); + for (t = v->load.saves.d, cont = NULL; t; t = t->n) { + l = t->data; + if (!cont || l->mod > cont->mod) + cont = l; + } + if ((v->cont.save = cont) != NULL) { + snprintf(v->cont.label, sizeof(v->cont.label), + "Continue (%s)", v->cont.save->name); + v->b[SMENU_CONT].enabled = 1; + } + if (v->load.saves.d) + v->b[SMENU_LOAD].enabled = 1; + v->load.init = 0; + } + + if (v->load.disp && v->load.savelist.sel) + v->load.load.enabled = 1; + else + v->load.load.enabled = 0; + + v->b[SMENU_SAVE].enabled = save ? 1 : 0; +} + +void +ui_draw_view_smenu(void) { + Color bg = { col_bg.r, col_bg.g, col_bg.b, 0xcc }; + int x, y, w, h; + int i; + + ui_draw_texture_part(image_splash, 0, 0, + MAX((image_splash.width - screen.w) / 2, 0), + MAX((image_splash.height - screen.h) / 2, 0), + screen.w, screen.h); + + if (v->new.disp) { + w = PAD * 2 + 300; + h = PAD * 3 + FONT_SIZE + BUTTON_HEIGHT; + x = (screen.w - w) / 2; + y = (screen.h - h) / 2; + + ui_draw_rect(x, y, w, h, col_bg); + ui_draw_border_around(x, y, w, h, 1); + + x += PAD; + y += PAD; + gui_input(x, y, 300, &v->new.name); + + x += w - 50 - PAD * 2, + gui_button(x, y + PAD * 2, 50, &v->new.create); + + x -= 50 + PAD; + gui_button(x, y + PAD * 2, 50, &v->new.back); + } else if (v->save.check) { + w = PAD * 2 + ui_textsize(v->save.msg); + h = PAD * 3 + FONT_SIZE + BUTTON_HEIGHT; + x = (screen.w - w) / 2; + y = (screen.h - h) / 2; + + ui_draw_rect(x, y, w, h, col_bg); + ui_draw_border_around(x, y, w, h, 1); + + x += PAD; + y += PAD; + ui_print(x, y, col_fg, "%s", v->save.msg); + + x += w - PAD * 2 - 50; + y += PAD * 2; + + gui_button(x, y, 50, &v->save.back); + gui_button(x -= 50 + PAD, y, 50, &v->save.discard); + gui_button(x -= 50 + PAD, y, 50, &v->save.save); + } else if (v->load.disp) { + w = PAD * 2 + LOAD_W; + h = PAD * 3 + LOAD_H + BUTTON_HEIGHT; + x = (screen.w - w) / 2; + y = (screen.h - h) / 2; + + ui_draw_rect(x, y, w, h, col_bg); + ui_draw_border_around(x, y, w, h, 1); + + x += PAD; + y += PAD; + + gui_treeview(x, y, LOAD_W, LOAD_H, &v->load.savelist); + gui_button(x + w - BUTTON_W - PAD * 2, + y + h - PAD * 2 - BUTTON_HEIGHT, + BUTTON_W, &v->load.load); + } else { + ui_draw_rect(EXPLODE_RECT(v->main), bg); + ui_draw_border_around(EXPLODE_RECT(v->main), 1); + + x = v->main.x + PAD; + y = v->main.y + PAD; + w = v->main.w - PAD * 2; + + for (i = 0; i < SMENU_LAST; i++) { + ui_draw_rect(x, y, w, BUTTON_HEIGHT, col_bg); + gui_button(x, y, w, &v->b[i]); + y += BUTTON_HEIGHT + PAD; + } + } +} diff --git a/src/views/struct.h b/src/views/struct.h @@ -1,3 +1,6 @@ +/* vim: set syntax=c : */ + +/* main/tactical */ typedef struct { struct { Tabs tabs; @@ -31,6 +34,7 @@ typedef struct { System *sys; } View_main; +/* bodies */ typedef struct { System *sys; Body *selstar; @@ -54,6 +58,7 @@ typedef struct { } prevframe; } View_bodies; +/* sys */ typedef struct { struct { Geom geom; @@ -65,3 +70,48 @@ typedef struct { } ly; System *sel; } View_sys; + +/* smenu */ +enum { + SMENU_NEW, + SMENU_SAVE, + SMENU_CONT, + SMENU_LOAD, + SMENU_QUIT, + SMENU_LAST +}; + +struct Loadable { + char *name; + time_t mod; +}; + +typedef struct { + Geom main; + Button b[SMENU_LAST]; + struct { + int disp; + Input name; + Button create; + Button back; + } new; + struct { + struct Loadable *save; + char label[128]; + } cont; + struct { + int check; + char *msg; + Button back; + Button save; + Button discard; + void (*func)(void); + } save; + struct { + int init; + int disp; + Tree saves; + Treeview savelist; + Button load; + } load; +} View_smenu;