ref: 7c273d147f0805abbf5753cb32800de176e21884
parent: b960255025fac920d12f89ef3493fb09ba2eb40b
author: laamaa <jonne.kokkonen@gmail.com>
date: Sat Sep 6 20:42:29 EDT 2025
preliminary config ui horrors
--- a/src/config.h
+++ b/src/config.h
@@ -65,4 +65,7 @@
void read_key_config(const ini_t *ini, config_params_s *conf);
void read_gamepad_config(const ini_t *ini, config_params_s *conf);
+// Expose write so settings UI can persist changes
+void write_config(const config_params_s *conf);
+
#endif
--- a/src/events.c
+++ b/src/events.c
@@ -4,6 +4,7 @@
#include "gamepads.h"
#include "input.h"
#include "render.h"
+#include "settings.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_events.h>
@@ -57,22 +58,48 @@
break;
case SDL_EVENT_KEY_DOWN:
+ // Toggle settings with F1
+ if (event->key.key == SDLK_F1 && event->key.repeat == 0) {+ settings_toggle_open();
+ return ret_val;
+ }
+ // Route to settings if open
+ if (settings_is_open()) {+ settings_handle_event(ctx, event);
+ return ret_val;
+ }
input_handle_key_down_event(ctx, event);
break;
case SDL_EVENT_KEY_UP:
+ if (settings_is_open()) {+ settings_handle_event(ctx, event);
+ return ret_val;
+ }
input_handle_key_up_event(ctx, event);
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
+ if (settings_is_open()) {+ settings_handle_event(ctx, event);
+ return ret_val;
+ }
input_handle_gamepad_button(ctx, event->gbutton.button, true);
break;
case SDL_EVENT_GAMEPAD_BUTTON_UP:
+ if (settings_is_open()) {+ settings_handle_event(ctx, event);
+ return ret_val;
+ }
input_handle_gamepad_button(ctx, event->gbutton.button, false);
break;
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
+ if (settings_is_open()) {+ settings_handle_event(ctx, event);
+ return ret_val;
+ }
input_handle_gamepad_axis(ctx, event->gaxis.axis, event->gaxis.value);
break;
--- a/src/render.c
+++ b/src/render.c
@@ -9,6 +9,7 @@
#include "command.h"
#include "config.h"
#include "fx_cube.h"
+#include "settings.h"
#include "fonts/font1.h"
#include "fonts/font2.h"
@@ -342,6 +343,9 @@
texture_width, texture_height);
SDL_SetTextureScaleMode(main_texture, texture_scaling_mode);
SDL_SetRenderTarget(rend, main_texture);
+
+ // Notify settings overlay about logical render size change so it can recreate its cache
+ settings_on_texture_size_change(rend);
}
// Set the M8 hardware model in use. 0 = MK1, 1 = MK2
@@ -615,8 +619,8 @@
}
void render_screen(config_params_s *conf) {- if (!dirty) {- // No draw commands have been issued since the last function call, do nothing
+ if (!dirty && !settings_is_open()) {+ // No draw commands and settings overlay not active, skip rendering
return;
}
@@ -719,6 +723,11 @@
if (log_texture) {SDL_RenderTexture(rend, log_texture, NULL, NULL);
}
+ }
+
+ // Settings overlay composited last
+ if (settings_is_open()) {+ settings_render_overlay(rend, conf, texture_width, texture_height);
}
if (!SDL_RenderPresent(rend)) {--- /dev/null
+++ b/src/settings.c
@@ -1,0 +1,418 @@
+#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; }+
+
+
--- /dev/null
+++ b/src/settings.h
@@ -1,0 +1,30 @@
+// Simple in-app settings overlay for configuring input bindings and a few options
+
+#ifndef SETTINGS_H_
+#define SETTINGS_H_
+
+#include <SDL3/SDL.h>
+#include <stdbool.h>
+
+#include "config.h"
+
+// Forward declaration to avoid header coupling
+struct app_context;
+
+// Open/close and state query
+void settings_toggle_open(void);
+bool settings_is_open(void);
+
+// Event handling (consume SDL events when open)
+void settings_handle_event(struct app_context *ctx, const SDL_Event *e);
+
+// Render the settings overlay into a texture-sized canvas and composite to the window
+// texture_w/texture_h should be the logical render size (e.g. 320x240 or 480x320)
+void settings_render_overlay(SDL_Renderer *rend, const config_params_s *conf, int texture_w, int texture_h);
+
+// Notify settings overlay that logical render size changed; drops cached texture
+void settings_on_texture_size_change(SDL_Renderer *rend);
+
+#endif // SETTINGS_H_
+
+
--
⑨