fda8556a39
This fixes a nasty bug that manifests on windows when building without precompiled headers. util/stringops.h used to silently replace strdup with a macro that's compatible with mem_free(). This header would typically be included everywhere due to PCH, but without it the strdup from libc would sometimes be in scope. On most platforms mem_free() is equivalent to free(), but not on windows, because we have to use _aligned_free() there. Attempting to mem_free() the result of a libc strdup() would segfault in such a configuration. Avoid the footgun by banning strdup() entirely. Maybe redefining libc names isn't such a great idea, who knew?
269 lines
5.1 KiB
C
269 lines
5.1 KiB
C
/*
|
|
* This software is licensed under the terms of the MIT License.
|
|
* See COPYING for further information.
|
|
* ---
|
|
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
|
|
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
|
|
*/
|
|
|
|
#include "menu.h"
|
|
|
|
#include "audio/audio.h"
|
|
#include "eventloop/eventloop.h"
|
|
#include "events.h"
|
|
#include "global.h"
|
|
#include "replay/demoplayer.h"
|
|
#include "util/graphics.h"
|
|
#include "video.h"
|
|
#include "watchdog.h"
|
|
|
|
MenuEntry *add_menu_entry(MenuData *menu, const char *name, MenuAction action, void *arg) {
|
|
return dynarray_append(&menu->entries, {
|
|
.action = action,
|
|
.arg = arg,
|
|
.transition = menu->transition,
|
|
.name = name ? mem_strdup(name) : NULL,
|
|
});
|
|
}
|
|
|
|
void add_menu_separator(MenuData *menu) {
|
|
dynarray_append(&menu->entries, {});
|
|
}
|
|
|
|
void free_menu(MenuData *menu) {
|
|
if(menu == NULL) {
|
|
return;
|
|
}
|
|
|
|
dynarray_foreach_elem(&menu->entries, MenuEntry *e, {
|
|
mem_free(e->name);
|
|
});
|
|
|
|
dynarray_free_data(&menu->entries);
|
|
mem_free(menu);
|
|
}
|
|
|
|
MenuData* alloc_menu(void) {
|
|
return ALLOC(MenuData, {
|
|
.selected = -1,
|
|
.transition = TransFadeBlack,
|
|
.transition_in_time = FADE_TIME,
|
|
.transition_out_time = FADE_TIME,
|
|
.fade = 1.0,
|
|
.input = menu_input,
|
|
});
|
|
}
|
|
|
|
void kill_menu(MenuData *menu) {
|
|
if(menu != NULL) {
|
|
menu->state = MS_Dead;
|
|
// nani?!
|
|
}
|
|
}
|
|
|
|
static void close_menu_finish(CallChainResult ccr) {
|
|
MenuData *menu = ccr.ctx;
|
|
|
|
if(TRANSITION_RESULT_CANCELED(ccr)) {
|
|
return;
|
|
}
|
|
|
|
// This may happen with MF_AlwaysProcessInput menus, so make absolutely sure we
|
|
// never run the call chain with menu->state == MS_Dead more than once.
|
|
bool was_dead = (menu->state == MS_Dead);
|
|
|
|
menu->state = MS_Dead;
|
|
|
|
if(menu->selected != -1) {
|
|
MenuEntry *e = dynarray_get_ptr(&menu->entries, menu->selected);
|
|
|
|
if(e->action != NULL) {
|
|
if(!(menu->flags & MF_Transient)) {
|
|
menu->state = MS_Normal;
|
|
}
|
|
|
|
e->action(menu, e->arg);
|
|
}
|
|
}
|
|
|
|
if(!was_dead) {
|
|
run_call_chain(&menu->cc, menu);
|
|
}
|
|
}
|
|
|
|
void close_menu(MenuData *menu) {
|
|
TransitionRule trans = menu->transition;
|
|
|
|
assert(menu->state != MS_Dead);
|
|
menu->state = MS_FadeOut;
|
|
|
|
if(menu->selected != -1) {
|
|
trans = dynarray_get(&menu->entries, menu->selected).transition;
|
|
}
|
|
|
|
CallChain cc = CALLCHAIN(close_menu_finish, menu);
|
|
|
|
if(trans) {
|
|
set_transition(
|
|
trans,
|
|
menu->transition_in_time,
|
|
menu->transition_out_time,
|
|
cc
|
|
);
|
|
} else {
|
|
run_call_chain(&cc, NULL);
|
|
}
|
|
}
|
|
|
|
float menu_fade(MenuData *menu) {
|
|
return transition.fade;
|
|
}
|
|
|
|
bool menu_input_handler(SDL_Event *event, void *arg) {
|
|
MenuData *menu = arg;
|
|
TaiseiEvent te = TAISEI_EVENT(event->type);
|
|
|
|
switch(te) {
|
|
case TE_MENU_CURSOR_DOWN: {
|
|
play_sfx_ui("generic_shot");
|
|
int orig_cursor = menu->cursor;
|
|
do {
|
|
if(++menu->cursor >= menu->entries.num_elements) {
|
|
menu->cursor = 0;
|
|
}
|
|
|
|
if(menu->cursor == orig_cursor) {
|
|
break;
|
|
}
|
|
} while(dynarray_get(&menu->entries, menu->cursor).action == NULL);
|
|
|
|
return true;
|
|
}
|
|
|
|
case TE_MENU_CURSOR_UP: {
|
|
play_sfx_ui("generic_shot");
|
|
int orig_cursor = menu->cursor;
|
|
do {
|
|
if(--menu->cursor < 0) {
|
|
menu->cursor = menu->entries.num_elements - 1;
|
|
}
|
|
|
|
if(menu->cursor == orig_cursor) {
|
|
break;
|
|
}
|
|
} while(dynarray_get(&menu->entries, menu->cursor).action == NULL);
|
|
|
|
return true;
|
|
}
|
|
|
|
case TE_MENU_ACCEPT:
|
|
play_sfx_ui("shot_special1");
|
|
if(dynarray_get(&menu->entries, menu->cursor).action) {
|
|
menu->selected = menu->cursor;
|
|
close_menu(menu);
|
|
}
|
|
|
|
return true;
|
|
|
|
case TE_MENU_ABORT:
|
|
play_sfx_ui("hit");
|
|
if(menu->flags & MF_Abortable) {
|
|
menu->selected = -1;
|
|
close_menu(menu);
|
|
}
|
|
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void menu_input(MenuData *menu) {
|
|
events_poll((EventHandler[]){
|
|
{ .proc = menu_input_handler, .arg = menu },
|
|
{ NULL }
|
|
}, EFLAG_MENU);
|
|
}
|
|
|
|
void menu_no_input(MenuData *menu) {
|
|
events_poll(NULL, 0);
|
|
}
|
|
|
|
static LogicFrameAction menu_logic_frame(void *arg) {
|
|
MenuData *menu = arg;
|
|
|
|
if(menu->state == MS_Dead) {
|
|
return LFRAME_STOP;
|
|
}
|
|
|
|
if(menu->logic) {
|
|
menu->logic(menu);
|
|
}
|
|
|
|
menu->frames++;
|
|
|
|
if(watchdog_signaled()) {
|
|
menu->selected = -1;
|
|
close_menu(menu);
|
|
}
|
|
|
|
if(menu->state != MS_FadeOut || menu->flags & MF_AlwaysProcessInput) {
|
|
assert(menu->input);
|
|
menu->input(menu);
|
|
} else {
|
|
menu_no_input(menu);
|
|
}
|
|
|
|
update_transition();
|
|
|
|
return LFRAME_WAIT;
|
|
}
|
|
|
|
static RenderFrameAction menu_render_frame(void *arg) {
|
|
MenuData *menu = arg;
|
|
assert(menu->draw);
|
|
set_ortho(SCREEN_W, SCREEN_H);
|
|
menu->draw(menu);
|
|
draw_transition();
|
|
return RFRAME_SWAP;
|
|
}
|
|
|
|
static void menu_end_loop(void *ctx) {
|
|
MenuData *menu = ctx;
|
|
|
|
if(menu->flags & MF_NoDemo) {
|
|
demoplayer_resume();
|
|
}
|
|
|
|
if(menu->state != MS_Dead) {
|
|
// definitely dead now...
|
|
menu->state = MS_Dead;
|
|
run_call_chain(&menu->cc, menu);
|
|
}
|
|
|
|
if(menu->end) {
|
|
menu->end(menu);
|
|
}
|
|
|
|
free_menu(menu);
|
|
}
|
|
|
|
void enter_menu(MenuData *menu, CallChain next) {
|
|
if(menu == NULL) {
|
|
run_call_chain(&next, NULL);
|
|
return;
|
|
}
|
|
|
|
menu->cc = next;
|
|
|
|
if(menu->begin != NULL) {
|
|
menu->begin(menu);
|
|
}
|
|
|
|
if(menu->flags & MF_NoDemo) {
|
|
demoplayer_suspend();
|
|
}
|
|
|
|
eventloop_enter(menu, menu_logic_frame, menu_render_frame, menu_end_loop, FPS);
|
|
}
|