Newer
Older
ESP32-RetroPlay / main / tasks / game / game_menu.c
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"

#include "game_menu.h"
#include "display_task.h" // To update the display with menu text
#include "game_framework.h" // For OLED_WIDTH, OLED_HEIGHT

#define MENU_TAG "GAME_MENU"

// Define these locally or ensure they are accessible from display_task.h
// Assuming N_DISPLAY_LINES and MAX_LINE_LENGTH are consistent with display_task.c
#define N_DISPLAY_LINES (OLED_HEIGHT / 8)
#define MAX_LINE_LENGTH (OLED_WIDTH / 8) + 1


// Helper function to draw the menu on the display
static void draw_menu(const menu_item_t menu_items[], size_t num_items, int selected_index) {
    // Array to hold all the strings for the display
    char menu_display_buffers[N_DISPLAY_LINES][MAX_LINE_LENGTH];
    const char *lines_to_send[N_DISPLAY_LINES];

    // Initialize all buffers to empty strings (filled with spaces for OLED clearing effect)
    for (int i = 0; i < N_DISPLAY_LINES; i++) {
        memset(menu_display_buffers[i], ' ', MAX_LINE_LENGTH - 1);
        menu_display_buffers[i][MAX_LINE_LENGTH - 1] = '\0';
        lines_to_send[i] = menu_display_buffers[i]; // Point to the buffer for sending
    }

    // Set menu title on the first line
    strncpy(menu_display_buffers[0], "  SELECT GAME:  ", MAX_LINE_LENGTH - 1);
    menu_display_buffers[0][MAX_LINE_LENGTH - 1] = '\0'; // Ensure null termination

    // Populate menu items starting from line 2
    for (size_t i = 0; i < num_items; i++) {
        int line_num = 2 + i; // Start displaying items from line 2
        if (line_num < N_DISPLAY_LINES) { // Ensure we don't write past screen
            const char *item_name = menu_items[i].name;
            size_t name_len = strlen(item_name);
            
            // Calculate starting position to center text
            int start_col = (MAX_LINE_LENGTH - 1 - name_len) / 2;
            if (start_col < 0) start_col = 0; // Prevent negative start_col for long names

            // Add selector if this is the selected item
            if (i == selected_index) {
                if (start_col >= 2) { // Ensure space for "> "
                    menu_display_buffers[line_num][start_col - 2] = '>';
                    menu_display_buffers[line_num][start_col - 1] = ' ';
                } else { // If not enough space, just put '>' at start
                    menu_display_buffers[line_num][0] = '>';
                    start_col = 1; // Adjust text start
                }
            }

            // Copy item name into buffer
            strncpy(&menu_display_buffers[line_num][start_col], item_name, MAX_LINE_LENGTH - 1 - start_col);
            menu_display_buffers[line_num][MAX_LINE_LENGTH - 1] = '\0'; // Ensure null termination
        }
    }

    // Send all prepared lines to the display task at once
    display_update_text(lines_to_send, N_DISPLAY_LINES);
}


const menu_item_t* run_game_menu(const menu_item_t menu_items[], size_t num_items, QueueHandle_t button_queue) {
    if (num_items == 0) {
        ESP_LOGE(MENU_TAG, "No menu items provided!");
        return NULL;
    }
    if (button_queue == NULL) {
        ESP_LOGE(MENU_TAG, "Button queue is NULL! Cannot navigate menu.");
        return NULL;
    }

    int selected_index = 0;
    draw_menu(menu_items, num_items, selected_index);

    // Static variables to track previous button states for edge detection
    static bool left_button_pressed_prev = false;
    static bool right_button_pressed_prev = false;

    while (true) {
        int input_signal = 0; // -1 for left, 1 for right, 0 for no movement
        // Wait indefinitely for a button press
        if (xQueueReceive(button_queue, &input_signal, portMAX_DELAY) == pdTRUE) {
            ESP_LOGI(MENU_TAG, "Menu input received: %d", input_signal);

            // Determine current button states from the continuous input signal
            bool left_button_now_pressed = (input_signal == -1);
            bool right_button_now_pressed = (input_signal == 1);

            // Detect rising edge (button press event)
            bool left_press_event = left_button_now_pressed && !left_button_pressed_prev;
            bool right_press_event = right_button_now_pressed && !right_button_pressed_prev;

            if (left_press_event) { // Left button pressed: Move cursor DOWN
                selected_index++;
                if (selected_index >= num_items) {
                    selected_index = 0; // Wrap around to the first item
                }
                draw_menu(menu_items, num_items, selected_index);
            } else if (right_press_event) { // Right button pressed: ENTER/SELECT game
                ESP_LOGI(MENU_TAG, "Game selected: %s", menu_items[selected_index].name);
                // Update previous button states before returning to prevent re-trigger on next menu entry
                left_button_pressed_prev = left_button_now_pressed;
                right_button_pressed_prev = right_button_now_pressed;
                return &menu_items[selected_index]; // Return pointer to the selected menu_item_t
            }
            
            // Update previous button states for the next iteration
            left_button_pressed_prev = left_button_now_pressed;
            right_button_pressed_prev = right_button_now_pressed;
        }
    }
}