Files
2026-07-24 21:04:22 +09:00

239 lines
6.2 KiB
C

/*
* menu.c popup menu
*
* Copyright (C) 2000- Masaki Chikama (Wren) <masaki-c@is.aist-nara.ac.jp>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "config.h"
#ifdef __EMSCRIPTEN__
#include <stdio.h>
#include <emscripten.h>
#include "portab.h"
#include "menu.h"
void menu_open(void) {
return;
}
void menu_quitmenu_open(void) {
return;
}
void menu_init(void) {
return;
}
EM_JS(void, menu_setSkipState, (bool enabled, bool activated), {
xsystem35.shell.setSkipButtonState(enabled, activated);
});
#else // !__EMSCRIPTEN__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include "portab.h"
#include "event.h"
#include "system.h"
#include "nact.h"
#include "menu.h"
#include "gfx.h"
#include "msgskip.h"
#include "modal.h"
#include "volume.h"
// The middle-click popup menu.
// What the popup menu resolved to.
enum menu_action {
MENU_ACTION_NONE,
MENU_ACTION_VOLUME,
MENU_ACTION_RESET,
MENU_ACTION_QUIT,
};
struct popup_state {
modal base;
mu_Rect rect; // popup window rect from the most recent frame
enum menu_action action; // what the user selected
// A three-finger tap opens the menu, but SDL_GetNumTouchFingers() reports the
// live finger count, so it already returns 3 while the first of the three
// near-simultaneous FINGERDOWN events is being processed - the menu opens on
// that first event. The other two FINGERDOWN events of the same gesture are
// still queued and, once the menu is up, would be seen as taps that dismiss
// it. Ignore touches until every finger has lifted, then arm tap-to-dismiss.
bool touch_armed;
};
#ifndef _WIN32 // Windows uses the native menu bar
// Message-skip state, kept in sync via menu_setSkipState().
static bool skip_enabled = true;
static bool skip_activated;
static bool point_in_rect(int x, int y, mu_Rect r) {
return x >= r.x && x < r.x + r.w && y >= r.y && y < r.y + r.h;
}
static bool menu_popup_handler(const SDL_Event *e, modal *modal) {
struct popup_state *st = (struct popup_state *)modal;
if (e->type == SDL_MOUSEBUTTONUP) {
if (e->button.button == SDL_BUTTON_RIGHT ||
!point_in_rect(e->button.x, e->button.y, st->rect))
st->base.cancelled = true;
} else if (e->type == SDL_FINGERUP) {
// Arm tap-to-dismiss once the opening gesture's fingers are all lifted.
if (SDL_GetNumTouchFingers(e->tfinger.touchId) == 0)
st->touch_armed = true;
} else if (e->type == SDL_FINGERDOWN && st->touch_armed) {
// A tap outside the menu dismisses it.
SDL_Point p = event_get_touch_position(&e->tfinger);
if (!point_in_rect(p.x, p.y, st->rect))
st->base.cancelled = true;
}
return modal_default_handler(e, modal);
}
static bool menu_build(mu_Context *ctx, modal *modal) {
struct popup_state *st = (struct popup_state *)modal;
bool keep_open = true;
int rows = 5;
#ifdef __ANDROID__
rows--;
#endif
int row_h = ctx->style->size.y + ctx->style->padding * 2;
int w = 200;
// `rows` rows with (rows - 1) inter-row gaps, plus the window's body padding.
int h = rows * row_h + (rows - 1) * ctx->style->spacing + ctx->style->padding * 2;
mu_Rect r = modal_centered_rect(w, h);
st->rect = r;
if (mu_begin_window_ex(ctx, "menu", r,
MU_OPT_NOTITLE | MU_OPT_NORESIZE | MU_OPT_NOCLOSE | MU_OPT_NOSCROLL)) {
mu_layout_row(ctx, 1, (int[]){ -1 }, 0);
if (skip_enabled) {
int sk = skip_activated;
if (mu_checkbox(ctx, _("Message Skip"), &sk))
msgskip_activate(sk);
} else {
modal_disabled_checkbox(ctx, _("Message Skip"), skip_activated);
}
#ifndef __ANDROID__
int warp = nact->ags.mouse_warp_enabled;
if (mu_checkbox(ctx, _("Mouse Movement"), &warp))
nact->ags.mouse_warp_enabled = warp;
#endif
if (mu_button(ctx, _("Sound Settings"))) {
keep_open = false;
st->action = MENU_ACTION_VOLUME;
}
if (mu_button(ctx, _("Restart"))) {
keep_open = false;
st->action = MENU_ACTION_RESET;
}
if (mu_button(ctx, _("Quit"))) {
keep_open = false;
st->action = MENU_ACTION_QUIT;
}
mu_end_window(ctx);
}
if (st->base.cancelled)
keep_open = false;
return keep_open;
}
void menu_open(void) {
struct popup_state st = {
.base = { .build = menu_build, .handler = menu_popup_handler },
.action = MENU_ACTION_NONE,
};
modal_run(&st.base);
switch (st.action) {
case MENU_ACTION_VOLUME:
volume_dialog_open();
break;
case MENU_ACTION_RESET:
menu_resetmenu_open();
break;
case MENU_ACTION_QUIT:
menu_quitmenu_open();
break;
case MENU_ACTION_NONE:
break;
}
}
#endif // !_WIN32
static bool confirm_lose_progress(const char *title, const char *confirm_label) {
const SDL_MessageBoxButtonData buttons[] = {
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, confirm_label },
{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 0, _("Cancel") },
};
const SDL_MessageBoxData messagebox_data = {
.flags = SDL_MESSAGEBOX_INFORMATION,
.window = gfx_getWindow(),
.title = title,
.message = _("Any unsaved progress will be lost."),
.numbuttons = SDL_arraysize(buttons),
.buttons = buttons,
};
int buttonid = 0;
if (SDL_ShowMessageBox(&messagebox_data, &buttonid) < 0) {
WARNING("error displaying message box");
return false;
}
return buttonid == 1;
}
void menu_quitmenu_open(void) {
if (confirm_lose_progress(_("Quit game?"), _("Quit")))
nact_quit(false);
}
void menu_resetmenu_open(void) {
if (confirm_lose_progress(_("Restart game?"), _("Restart")))
nact_quit(true);
}
// On Windows, menu_open/menu_init/menu_setSkipState are provided by win/menubar.c.
#ifndef _WIN32
void menu_init(void) {
}
void menu_setSkipState(bool enabled, bool activated) {
skip_enabled = enabled;
skip_activated = activated;
}
#endif
#endif // !__EMSCRIPTEN__