shithub: m8c

Download patch

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_
+
+
--