shithub: m8c

ref: 7c273d147f0805abbf5753cb32800de176e21884
dir: /src/settings.c/

View raw version
#include "settings.h"

#include "SDL2_inprint.h"
#include "common.h"
#include <SDL3/SDL.h>

// Internal state
static int g_settings_open = 0;
static int g_selected_index = 0;
static int g_needs_redraw = 1;
static SDL_Texture *g_settings_texture = NULL;

typedef enum capture_mode_t {
	CAPTURE_NONE,
	CAPTURE_KEY,
	CAPTURE_BUTTON,
	CAPTURE_AXIS
} capture_mode_t;

static capture_mode_t g_capture_mode = CAPTURE_NONE;
static void *g_capture_target = NULL; // points to conf field

typedef enum item_type_t {
	ITEM_HEADER,
	ITEM_TOGGLE_BOOL,
	ITEM_SAVE,
	ITEM_CLOSE,
	ITEM_SUBMENU,
	ITEM_BIND_KEY,
	ITEM_BIND_BUTTON,
	ITEM_BIND_AXIS,
	ITEM_INT_ADJ
} item_type_t;

typedef struct setting_item_s {
	const char *label;
	item_type_t type;
	void *target;
	int step;
	int min_val;
	int max_val;
} setting_item_s;

typedef enum settings_view_t { VIEW_ROOT, VIEW_KEYS, VIEW_GAMEPAD, VIEW_ANALOG } settings_view_t;
static settings_view_t g_view = VIEW_ROOT;

static void add_item(setting_item_s *items, int *count, const char *label, item_type_t type, void *target,
			 int step, int min_val, int max_val) {
	items[*count] = (setting_item_s){label, type, target, step, min_val, max_val};
	(*count)++;
}

static void build_menu(const config_params_s *conf, setting_item_s *items, int *count) {
    *count = 0;
    switch (g_view) {
    case VIEW_ROOT:
        add_item(items, count, "Graphics", ITEM_HEADER, NULL, 0, 0, 0);
        add_item(items, count, "Integer scaling", ITEM_TOGGLE_BOOL, (void *)&conf->integer_scaling, 0, 0, 0);
        add_item(items, count, "Fullscreen", ITEM_TOGGLE_BOOL, (void *)&conf->init_fullscreen, 0, 0, 0);

        add_item(items, count, "Audio", ITEM_HEADER, NULL, 0, 0, 0);
        add_item(items, count, "Audio enabled", ITEM_TOGGLE_BOOL, (void *)&conf->audio_enabled, 0, 0, 0);

        add_item(items, count, "Bindings", ITEM_HEADER, NULL, 0, 0, 0);
        add_item(items, count, "Keyboard bindings >", ITEM_SUBMENU, NULL, 0, 0, 0);
        add_item(items, count, "Gamepad bindings >", ITEM_SUBMENU, NULL, 0, 0, 0);
        add_item(items, count, "Analog settings >", ITEM_SUBMENU, NULL, 0, 0, 0);

        add_item(items, count, "Save", ITEM_SAVE, NULL, 0, 0, 0);
        add_item(items, count, "Close", ITEM_CLOSE, NULL, 0, 0, 0);
        break;
    case VIEW_KEYS:
        add_item(items, count, "Keyboard bindings", ITEM_HEADER, NULL, 0, 0, 0);
        add_item(items, count, "Up", ITEM_BIND_KEY, (void *)&conf->key_up, 0, 0, 0);
        add_item(items, count, "Left", ITEM_BIND_KEY, (void *)&conf->key_left, 0, 0, 0);
        add_item(items, count, "Down", ITEM_BIND_KEY, (void *)&conf->key_down, 0, 0, 0);
        add_item(items, count, "Right", ITEM_BIND_KEY, (void *)&conf->key_right, 0, 0, 0);
        add_item(items, count, "Select", ITEM_BIND_KEY, (void *)&conf->key_select, 0, 0, 0);
        add_item(items, count, "Start", ITEM_BIND_KEY, (void *)&conf->key_start, 0, 0, 0);
        add_item(items, count, "Opt", ITEM_BIND_KEY, (void *)&conf->key_opt, 0, 0, 0);
        add_item(items, count, "Edit", ITEM_BIND_KEY, (void *)&conf->key_edit, 0, 0, 0);
        add_item(items, count, "Delete", ITEM_BIND_KEY, (void *)&conf->key_delete, 0, 0, 0);
        add_item(items, count, "Reset", ITEM_BIND_KEY, (void *)&conf->key_reset, 0, 0, 0);
        add_item(items, count, "Toggle audio", ITEM_BIND_KEY, (void *)&conf->key_toggle_audio, 0, 0, 0);
        add_item(items, count, "Toggle log", ITEM_BIND_KEY, (void *)&conf->key_toggle_log, 0, 0, 0);
        add_item(items, count, "Back", ITEM_CLOSE, NULL, 0, 0, 0);
        break;
    case VIEW_GAMEPAD:
        add_item(items, count, "Gamepad bindings", ITEM_HEADER, NULL, 0, 0, 0);
        add_item(items, count, "Up", ITEM_BIND_BUTTON, (void *)&conf->gamepad_up, 0, 0, 0);
        add_item(items, count, "Left", ITEM_BIND_BUTTON, (void *)&conf->gamepad_left, 0, 0, 0);
        add_item(items, count, "Down", ITEM_BIND_BUTTON, (void *)&conf->gamepad_down, 0, 0, 0);
        add_item(items, count, "Right", ITEM_BIND_BUTTON, (void *)&conf->gamepad_right, 0, 0, 0);
        add_item(items, count, "Select", ITEM_BIND_BUTTON, (void *)&conf->gamepad_select, 0, 0, 0);
        add_item(items, count, "Start", ITEM_BIND_BUTTON, (void *)&conf->gamepad_start, 0, 0, 0);
        add_item(items, count, "Opt", ITEM_BIND_BUTTON, (void *)&conf->gamepad_opt, 0, 0, 0);
        add_item(items, count, "Edit", ITEM_BIND_BUTTON, (void *)&conf->gamepad_edit, 0, 0, 0);
        add_item(items, count, "Quit", ITEM_BIND_BUTTON, (void *)&conf->gamepad_quit, 0, 0, 0);
        add_item(items, count, "Reset", ITEM_BIND_BUTTON, (void *)&conf->gamepad_reset, 0, 0, 0);
        add_item(items, count, "Back", ITEM_CLOSE, NULL, 0, 0, 0);
        break;
    case VIEW_ANALOG:
        add_item(items, count, "Analog settings", ITEM_HEADER, NULL, 0, 0, 0);
        add_item(items, count, "Analog threshold", ITEM_INT_ADJ, (void *)&conf->gamepad_analog_threshold, 1000, 1000, 32767);
        add_item(items, count, "Axis Up/Down", ITEM_BIND_AXIS, (void *)&conf->gamepad_analog_axis_updown, 0, 0, 0);
        add_item(items, count, "Axis Left/Right", ITEM_BIND_AXIS, (void *)&conf->gamepad_analog_axis_leftright, 0, 0, 0);
        add_item(items, count, "Axis Select", ITEM_BIND_AXIS, (void *)&conf->gamepad_analog_axis_select, 0, 0, 0);
        add_item(items, count, "Axis Start", ITEM_BIND_AXIS, (void *)&conf->gamepad_analog_axis_start, 0, 0, 0);
        add_item(items, count, "Axis Opt", ITEM_BIND_AXIS, (void *)&conf->gamepad_analog_axis_opt, 0, 0, 0);
        add_item(items, count, "Axis Edit", ITEM_BIND_AXIS, (void *)&conf->gamepad_analog_axis_edit, 0, 0, 0);
        add_item(items, count, "Back", ITEM_CLOSE, NULL, 0, 0, 0);
        break;
    }
}

static void settings_destroy_texture(SDL_Renderer *rend) {
	(void)rend;
	if (g_settings_texture != NULL) {
		SDL_DestroyTexture(g_settings_texture);
		g_settings_texture = NULL;
	}
}

void settings_toggle_open(void) {
	g_settings_open = !g_settings_open;
	g_selected_index = 1; // first actionable item
	g_capture_mode = CAPTURE_NONE;
	g_capture_target = NULL;
	g_needs_redraw = 1;
}

bool settings_is_open(void) { return g_settings_open != 0; }

static void settings_move(int delta) {
	if (!g_settings_open)
		return;
	// We will clamp after building the menu in render
	g_selected_index += delta;
	if (g_selected_index < 1)
		g_selected_index = 1;
	g_needs_redraw = 1;
}

static void settings_activate(struct app_context *ctx, const setting_item_s *items, int count) {
	if (g_selected_index < 1 || g_selected_index >= count)
		return;
	const setting_item_s *it = &items[g_selected_index];
	config_params_s *conf = &ctx->conf;

	switch (it->type) {
	case ITEM_TOGGLE_BOOL: {
		unsigned int *val = (unsigned int *)it->target;
		*val = *val ? 0 : 1;
		if (it->target == &conf->init_fullscreen) {
			extern void toggle_fullscreen(void);
			toggle_fullscreen();
		}
		g_needs_redraw = 1;
		break;
	}
	case ITEM_SAVE: {
		extern void write_config(const config_params_s *conf);
		write_config(conf);
		g_needs_redraw = 1;
		break;
	}
	case ITEM_CLOSE:
		settings_toggle_open();
		break;
	case ITEM_BIND_KEY:
		g_capture_mode = CAPTURE_KEY;
		g_capture_target = it->target;
		g_needs_redraw = 1;
		break;
	case ITEM_BIND_BUTTON:
		g_capture_mode = CAPTURE_BUTTON;
		g_capture_target = it->target;
		g_needs_redraw = 1;
		break;
	case ITEM_BIND_AXIS:
		g_capture_mode = CAPTURE_AXIS;
		g_capture_target = it->target;
		g_needs_redraw = 1;
		break;
	case ITEM_INT_ADJ:
	case ITEM_HEADER:
		break;
	}
}

void settings_handle_event(struct app_context *ctx, const SDL_Event *e) {
	if (!g_settings_open)
		return;

	if (e->type == SDL_EVENT_KEY_DOWN) {
		if (e->key.key == SDLK_ESCAPE || e->key.key == SDLK_F1) {
			if (g_capture_mode != CAPTURE_NONE) {
				g_capture_mode = CAPTURE_NONE;
				g_capture_target = NULL;
				g_needs_redraw = 1;
				return;
			}
			if (g_view != VIEW_ROOT) {
				g_view = VIEW_ROOT;
				g_selected_index = 1;
				g_needs_redraw = 1;
				return;
			}
			settings_toggle_open();
			return;
		}
		// Capture key remap
		if (g_capture_mode == CAPTURE_KEY) {
			if (g_capture_target != NULL) {
				unsigned int *dst = (unsigned int *)g_capture_target;
				*dst = e->key.scancode;
			}
			g_capture_mode = CAPTURE_NONE;
			g_capture_target = NULL;
			g_needs_redraw = 1;
			return;
		}
		if (e->key.key == SDLK_UP) {
			settings_move(-1);
			return;
		}
		if (e->key.key == SDLK_DOWN) {
			settings_move(1);
			return;
		}
		if (e->key.key == SDLK_LEFT || e->key.key == SDLK_RIGHT) {
			setting_item_s items[64];
			int count = 0;
			build_menu(&ctx->conf, items, &count);
			if (g_selected_index > 0 && g_selected_index < count) {
				setting_item_s *it = &items[g_selected_index];
				if (it->type == ITEM_INT_ADJ && it->target != NULL) {
					int *val = (int *)it->target;
					int delta = (e->key.key == SDLK_LEFT ? -it->step : it->step);
					int newv = *val + delta;
					if (newv < it->min_val) newv = it->min_val;
					if (newv > it->max_val) newv = it->max_val;
					*val = newv;
					g_needs_redraw = 1;
					return;
				}
			}
		}
		if (e->key.key == SDLK_RETURN || e->key.key == SDLK_SPACE) {
			setting_item_s items[64];
			int count = 0;
			build_menu(&ctx->conf, items, &count);
			// Handle entering submenus from root based on item type
			if (g_view == VIEW_ROOT) {
				const setting_item_s *it = &items[g_selected_index];
				if (it->type == ITEM_SUBMENU && it->label && SDL_strstr(it->label, "Keyboard bindings") == it->label) {
					g_view = VIEW_KEYS;
					g_selected_index = 1;
					g_needs_redraw = 1;
					return;
				}
				if (it->type == ITEM_SUBMENU && it->label && SDL_strstr(it->label, "Gamepad bindings") == it->label) {
					g_view = VIEW_GAMEPAD;
					g_selected_index = 1;
					g_needs_redraw = 1;
					return;
				}
				if (it->type == ITEM_SUBMENU && it->label && SDL_strstr(it->label, "Analog settings") == it->label) {
					g_view = VIEW_ANALOG;
					g_selected_index = 1;
					g_needs_redraw = 1;
					return;
				}
			}
			// Back item in submenus
			if (g_view != VIEW_ROOT) {
				const setting_item_s *it = &items[g_selected_index];
				if (it->type == ITEM_CLOSE && it->label && SDL_strcmp(it->label, "Back") == 0) {
					g_view = VIEW_ROOT;
					g_selected_index = 1;
					g_needs_redraw = 1;
					return;
				}
			}
			settings_activate(ctx, items, count);
			return;
		}
	}

	// Capture gamepad button
	if (g_capture_mode == CAPTURE_BUTTON && e->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
		if (g_capture_target != NULL) {
			int *dst = (int *)g_capture_target;
			*dst = e->gbutton.button;
		}
		g_capture_mode = CAPTURE_NONE;
		g_capture_target = NULL;
		g_needs_redraw = 1;
		return;
	}
	// Capture axis on significant motion
	if (g_capture_mode == CAPTURE_AXIS && e->type == SDL_EVENT_GAMEPAD_AXIS_MOTION) {
		if (SDL_abs(e->gaxis.value) > 16000) {
			if (g_capture_target != NULL) {
				int *dst = (int *)g_capture_target;
				*dst = e->gaxis.axis;
			}
			g_capture_mode = CAPTURE_NONE;
			g_capture_target = NULL;
			g_needs_redraw = 1;
			return;
		}
	}
}

void settings_render_overlay(SDL_Renderer *rend, const config_params_s *conf, int texture_w,
				int texture_h) {
	if (!g_settings_open)
		return;

	if (g_settings_texture == NULL) {
		g_settings_texture = SDL_CreateTexture(rend, SDL_PIXELFORMAT_ARGB8888,
						 SDL_TEXTUREACCESS_TARGET, texture_w, texture_h);
		if (g_settings_texture == NULL) {
			return;
		}
		SDL_SetTextureBlendMode(g_settings_texture, SDL_BLENDMODE_BLEND);
		SDL_SetTextureScaleMode(g_settings_texture, SDL_SCALEMODE_NEAREST);
		g_needs_redraw = 1;
	}

	if (!g_needs_redraw)
		goto composite;
	g_needs_redraw = 0;

	SDL_Texture *prev = SDL_GetRenderTarget(rend);
	SDL_SetRenderTarget(rend, g_settings_texture);

	// Background
	SDL_SetRenderDrawColor(rend, 0, 0, 0, 200);
	SDL_RenderClear(rend);

	// Title and items
	const Uint32 fg = 0xFFFFFF;
	const Uint32 bg = 0x000000;
	int x = 8;
	int y = 8;

	inprint(rend, "Settings", x, y, 0xFFFF00, bg);
	y += 14;

	setting_item_s items[64];
	int count = 0;
	build_menu(conf, items, &count);
	if (g_selected_index >= count) g_selected_index = count - 1;

	if (g_capture_mode == CAPTURE_KEY) {
		inprint(rend, "Press a key... (Esc to cancel)", x, y, 0x00FFFF, bg);
		y += 12;
	} else if (g_capture_mode == CAPTURE_BUTTON) {
		inprint(rend, "Press a gamepad button...", x, y, 0x00FFFF, bg);
		y += 12;
	} else if (g_capture_mode == CAPTURE_AXIS) {
		inprint(rend, "Move a gamepad axis...", x, y, 0x00FFFF, bg);
		y += 12;
	}

	for (int i = 0; i < count; i++) {
		const int selected = (i == g_selected_index);
		const setting_item_s *it = &items[i];
		if (it->type == ITEM_HEADER) {
			inprint(rend, it->label, x, y, 0xAAAAFF, bg);
			y += 12;
			continue;
		}
		if (it->type == ITEM_TOGGLE_BOOL) {
			const unsigned int *val = (const unsigned int *)it->target;
			char line[160];
			SDL_snprintf(line, sizeof line, "%s [%s]", it->label, (*val ? "On" : "Off"));
			inprint(rend, line, x + (selected ? 0 : 2), y, selected ? 0x00FF00 : fg, bg);
			y += 12;
			continue;
		}
		if (it->type == ITEM_BIND_KEY || it->type == ITEM_BIND_BUTTON || it->type == ITEM_BIND_AXIS) {
			int v = *(int *)it->target;
			char line[160];
			SDL_snprintf(line, sizeof line, "%s: %d", it->label, v);
			inprint(rend, line, x + (selected ? 0 : 2), y, selected ? 0x00FF00 : fg, bg);
			y += 12;
			continue;
		}
		if (it->type == ITEM_INT_ADJ) {
			char line[160];
			SDL_snprintf(line, sizeof line, "%s: %d  (Left/Right)", it->label, *(int *)it->target);
			inprint(rend, line, x + (selected ? 0 : 2), y, selected ? 0x00FF00 : fg, bg);
			y += 12;
			continue;
		}
		if (it->type == ITEM_SAVE || it->type == ITEM_CLOSE) {
			char line[160];
			SDL_snprintf(line, sizeof line, "%s", it->label);
			inprint(rend, line, x + (selected ? 0 : 2), y, selected ? 0x00FF00 : fg, bg);
			y += 12;
			continue;
		}
	}

	SDL_SetRenderTarget(rend, prev);

composite:
	SDL_RenderTexture(rend, g_settings_texture, NULL, NULL);
}

// Handle renderer/size resets: drop the cached texture to recreate at new size on next frame
void settings_on_texture_size_change(SDL_Renderer *rend) { settings_destroy_texture(rend); g_needs_redraw = 1; }