初始化版本
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
.cache/*
|
||||
build/*
|
||||
|
||||
25
CMakeLists.txt
Normal file
25
CMakeLists.txt
Normal file
@ -0,0 +1,25 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(MenuSystem C)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
|
||||
include_directories(
|
||||
api
|
||||
src/core
|
||||
src/param
|
||||
src/lang
|
||||
port
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE SOURCES
|
||||
api/*.c
|
||||
src/core/*.c
|
||||
src/param/*.c
|
||||
src/lang/*.c
|
||||
port/*.c
|
||||
)
|
||||
|
||||
add_library(menu STATIC ${SOURCES})
|
||||
|
||||
add_executable(menu_demo examples/demo.c)
|
||||
target_link_libraries(menu_demo menu)
|
||||
115
api/menu_api.c
Normal file
115
api/menu_api.c
Normal file
@ -0,0 +1,115 @@
|
||||
#include "menu_api.h"
|
||||
#include "../src/core/menu_core.h"
|
||||
#include "../src/core/menu_event.h"
|
||||
#include "../src/core/menu_stack.h"
|
||||
#include <stddef.h>
|
||||
|
||||
static MenuCoreCtx g_menu_ctx;
|
||||
|
||||
MenuErrCode menu_init(void) {
|
||||
return menu_core_init(&g_menu_ctx);
|
||||
}
|
||||
|
||||
void menu_deinit(void) {
|
||||
// Cleanup if needed
|
||||
}
|
||||
|
||||
void menu_main_loop(uint32_t tick) {
|
||||
menu_core_loop(&g_menu_ctx, tick);
|
||||
}
|
||||
|
||||
MenuNodeId menu_register_node(
|
||||
MenuNodeId id,
|
||||
MenuNodeId parent_id,
|
||||
const char* name,
|
||||
MenuCallback enter_cb,
|
||||
MenuCallback exit_cb
|
||||
) {
|
||||
return menu_register_node_ex(id, parent_id, name, enter_cb, exit_cb, NULL, NULL);
|
||||
}
|
||||
|
||||
MenuNodeId menu_register_node_ex(
|
||||
MenuNodeId id,
|
||||
MenuNodeId parent_id,
|
||||
const char* name,
|
||||
MenuCallback enter_cb,
|
||||
MenuCallback exit_cb,
|
||||
MenuCallback render_cb,
|
||||
void* user_data
|
||||
) {
|
||||
MenuNode node = {0};
|
||||
node.id = id;
|
||||
node.parent_id = parent_id;
|
||||
node.name = name;
|
||||
node.enter_cb = enter_cb;
|
||||
node.exit_cb = exit_cb;
|
||||
node.render_cb = render_cb;
|
||||
node.user_data = user_data;
|
||||
node.flags.is_enabled = true;
|
||||
node.flags.is_visible = true;
|
||||
node.flags.is_registered = false; // Will be set by core
|
||||
node.param_id = 0;
|
||||
node.permission_level = 0;
|
||||
|
||||
MenuNodeId new_id = 0;
|
||||
if (menu_core_register_node(&g_menu_ctx, &node, &new_id) == MENU_ERR_OK) {
|
||||
return new_id;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
MenuErrCode menu_post_event(MenuEventType type, uint32_t param) {
|
||||
return menu_event_post(&g_menu_ctx.event_queue, type, param);
|
||||
}
|
||||
|
||||
MenuErrCode menu_enter(void) {
|
||||
// We try to enter the first available root node.
|
||||
// We can trigger an event or just let the loop handle it.
|
||||
// If we rely on handle_event to enter root on NULL current,
|
||||
// we just need to ensure loop is called.
|
||||
// But explicitly:
|
||||
if (g_menu_ctx.current_node_id == 0) {
|
||||
// Force finding a root
|
||||
// Since we don't have public access to ctx nodes from here easily without exposing everything,
|
||||
// we can just call handle_event with a dummy event or NONE event if handle_event checks NULL current.
|
||||
// But handle_event checks event type.
|
||||
// We can add MENU_EVENT_NONE handling in handle_event to check entry?
|
||||
// Or just manually find root here.
|
||||
// We have access to g_menu_ctx.
|
||||
for(int i=0; i<MENU_CONFIG_MAX_NODES; i++) {
|
||||
if(g_menu_ctx.nodes[i].flags.is_registered && g_menu_ctx.nodes[i].parent_id == 0) {
|
||||
// Enter root
|
||||
MenuErrCode ret = menu_core_enter_node(&g_menu_ctx, g_menu_ctx.nodes[i].id);
|
||||
if (ret != MENU_ERR_OK) return ret;
|
||||
|
||||
// Optimization: Automatically enter first child if root has children
|
||||
// This makes the menu start with the first item selected
|
||||
if (g_menu_ctx.nodes[i].first_child_id != 0) {
|
||||
// We need to simulate Enter key or just manually transition
|
||||
// Manual transition:
|
||||
// Push root to stack
|
||||
// But menu_core_enter_node logic doesn't push to stack automatically unless via handle_event
|
||||
// So we mimic handle_event logic
|
||||
menu_stack_push(&g_menu_ctx.stack, g_menu_ctx.nodes[i].id);
|
||||
// Exit root (deselect)
|
||||
menu_core_exit_node(&g_menu_ctx, g_menu_ctx.nodes[i].id);
|
||||
// Enter child
|
||||
return menu_core_enter_node(&g_menu_ctx, g_menu_ctx.nodes[i].first_child_id);
|
||||
}
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_back(void) {
|
||||
return menu_post_event(MENU_EVENT_KEY_ESC, 0);
|
||||
}
|
||||
|
||||
MenuErrCode menu_node_bind_param(MenuNodeId node_id, uint16_t param_id) {
|
||||
MenuNode* node = menu_core_get_node(&g_menu_ctx, node_id);
|
||||
if (!node) return MENU_ERR_NOT_FOUND;
|
||||
node->param_id = param_id;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
51
api/menu_api.h
Normal file
51
api/menu_api.h
Normal file
@ -0,0 +1,51 @@
|
||||
#ifndef MENU_API_H
|
||||
#define MENU_API_H
|
||||
|
||||
#include "../src/core/menu_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Initialization
|
||||
MenuErrCode menu_init(void);
|
||||
void menu_deinit(void);
|
||||
|
||||
// Core Loop
|
||||
void menu_main_loop(uint32_t tick);
|
||||
|
||||
// Node Registration
|
||||
MenuNodeId menu_register_node(
|
||||
MenuNodeId id,
|
||||
MenuNodeId parent_id,
|
||||
const char* name,
|
||||
MenuCallback enter_cb,
|
||||
MenuCallback exit_cb
|
||||
);
|
||||
|
||||
// Extended registration (Optimization)
|
||||
MenuNodeId menu_register_node_ex(
|
||||
MenuNodeId id,
|
||||
MenuNodeId parent_id,
|
||||
const char* name,
|
||||
MenuCallback enter_cb,
|
||||
MenuCallback exit_cb,
|
||||
MenuCallback render_cb,
|
||||
void* user_data
|
||||
);
|
||||
|
||||
// Event Handling
|
||||
MenuErrCode menu_post_event(MenuEventType type, uint32_t param);
|
||||
|
||||
// Navigation
|
||||
MenuErrCode menu_enter(void); // Enter root
|
||||
MenuErrCode menu_back(void);
|
||||
|
||||
// Parameter Binding
|
||||
MenuErrCode menu_node_bind_param(MenuNodeId node_id, uint16_t param_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // MENU_API_H
|
||||
119
examples/demo.c
Normal file
119
examples/demo.c
Normal file
@ -0,0 +1,119 @@
|
||||
#include "menu_api.h"
|
||||
#include "menu_param.h"
|
||||
#include "menu_lang.h"
|
||||
#include "menu_port.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Globals
|
||||
int32_t g_volume = 50;
|
||||
int32_t g_brightness = 80;
|
||||
uint8_t g_language = 0; // 0=EN, 1=CN
|
||||
|
||||
// Callbacks
|
||||
MenuErrCode on_enter_main(MenuNodeId id, struct MenuCoreCtx* ctx) {
|
||||
menu_port_log(">> Entered Main Menu");
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode on_enter_settings(MenuNodeId id, struct MenuCoreCtx* ctx) {
|
||||
menu_port_log(">> Entered Settings");
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode on_enter_volume(MenuNodeId id, struct MenuCoreCtx* ctx) {
|
||||
menu_port_log(">> Entered Volume Param Edit");
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode on_enter_brightness(MenuNodeId id, struct MenuCoreCtx* ctx) {
|
||||
menu_port_log(">> Entered Brightness Param Edit");
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode on_render(MenuNodeId id, struct MenuCoreCtx* ctx) {
|
||||
// In a real system, this would draw to screen
|
||||
// Here we just print the current state occasionally or on change
|
||||
// For demo simplicity, we rely on enter/exit logs
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
void setup_params(void) {
|
||||
menu_param_init();
|
||||
|
||||
MenuParam p_vol = {0};
|
||||
p_vol.id = 1;
|
||||
p_vol.type = MENU_PARAM_TYPE_INT32;
|
||||
p_vol.name = "Volume";
|
||||
p_vol.value_ptr = &g_volume;
|
||||
p_vol.meta.num.min = 0;
|
||||
p_vol.meta.num.max = 100;
|
||||
p_vol.meta.num.step = 5;
|
||||
menu_param_register(&p_vol);
|
||||
|
||||
MenuParam p_bri = {0};
|
||||
p_bri.id = 2;
|
||||
p_bri.type = MENU_PARAM_TYPE_INT32;
|
||||
p_bri.name = "Brightness";
|
||||
p_bri.value_ptr = &g_brightness;
|
||||
p_bri.meta.num.min = 10;
|
||||
p_bri.meta.num.max = 100;
|
||||
p_bri.meta.num.step = 10;
|
||||
menu_param_register(&p_bri);
|
||||
}
|
||||
|
||||
void setup_menu(void) {
|
||||
menu_init();
|
||||
|
||||
// Root: Main Menu
|
||||
MenuNodeId main = menu_register_node(0, 0, "$MAIN", on_enter_main, NULL);
|
||||
|
||||
// Child: Settings
|
||||
MenuNodeId settings = menu_register_node(0, main, "$SETTING", on_enter_settings, NULL);
|
||||
|
||||
// Child: Info
|
||||
MenuNodeId info = menu_register_node(0, main, "$INFO", NULL, NULL);
|
||||
|
||||
// Child of Settings: Volume
|
||||
MenuNodeId vol_node = menu_register_node(0, settings, "Volume", on_enter_volume, NULL);
|
||||
menu_node_bind_param(vol_node, 1); // Bind to Volume param (ID 1)
|
||||
|
||||
// Child of Settings: Brightness
|
||||
MenuNodeId bri_node = menu_register_node(0, settings, "Brightness", on_enter_brightness, NULL);
|
||||
menu_node_bind_param(bri_node, 2); // Bind to Brightness param (ID 2)
|
||||
}
|
||||
|
||||
// Stub for binding param (will implement in API)
|
||||
void bind_param(MenuNodeId node, uint16_t param) {
|
||||
// Need API support
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
menu_port_init();
|
||||
setup_params();
|
||||
setup_menu();
|
||||
|
||||
menu_port_log("Menu System Demo Started");
|
||||
menu_enter(); // Enter root
|
||||
|
||||
// Simulation Loop
|
||||
int steps = 0;
|
||||
while (steps < 20) {
|
||||
menu_main_loop(0);
|
||||
|
||||
// Simulate inputs
|
||||
if (steps == 2) {
|
||||
menu_port_log("User Press: ENTER");
|
||||
menu_post_event(MENU_EVENT_KEY_ENTER, 0);
|
||||
}
|
||||
if (steps == 4) {
|
||||
menu_port_log("User Press: DOWN");
|
||||
menu_post_event(MENU_EVENT_KEY_DOWN, 0);
|
||||
}
|
||||
|
||||
steps++;
|
||||
// sleep
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
21
port/menu_port.c
Normal file
21
port/menu_port.c
Normal file
@ -0,0 +1,21 @@
|
||||
#include "menu_port.h"
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
|
||||
void menu_port_init(void) {
|
||||
// Platform specific init
|
||||
}
|
||||
|
||||
uint32_t menu_port_get_tick(void) {
|
||||
// Return ms
|
||||
return (uint32_t)(clock() * 1000 / CLOCKS_PER_SEC);
|
||||
}
|
||||
|
||||
void menu_port_log(const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vprintf(fmt, args);
|
||||
va_end(args);
|
||||
printf("\n");
|
||||
}
|
||||
18
port/menu_port.h
Normal file
18
port/menu_port.h
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef MENU_PORT_H
|
||||
#define MENU_PORT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void menu_port_init(void);
|
||||
uint32_t menu_port_get_tick(void);
|
||||
void menu_port_log(const char* fmt, ...);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // MENU_PORT_H
|
||||
49
src/core/menu_config.h
Normal file
49
src/core/menu_config.h
Normal file
@ -0,0 +1,49 @@
|
||||
#ifndef MENU_CONFIG_H
|
||||
#define MENU_CONFIG_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Maximum number of menu nodes
|
||||
#ifndef MENU_CONFIG_MAX_NODES
|
||||
#define MENU_CONFIG_MAX_NODES 32
|
||||
#endif
|
||||
|
||||
// Maximum stack depth for navigation
|
||||
#ifndef MENU_CONFIG_STACK_DEPTH
|
||||
#define MENU_CONFIG_STACK_DEPTH 8
|
||||
#endif
|
||||
|
||||
// Event queue length
|
||||
#ifndef MENU_CONFIG_EVENT_QUEUE_LEN
|
||||
#define MENU_CONFIG_EVENT_QUEUE_LEN 16
|
||||
#endif
|
||||
|
||||
// Hash table size (should be a power of 2 for efficiency)
|
||||
#ifndef MENU_CONFIG_HASH_TABLE_SIZE
|
||||
#define MENU_CONFIG_HASH_TABLE_SIZE 16
|
||||
#endif
|
||||
|
||||
// Feature switches
|
||||
#ifndef MENU_CONFIG_ENABLE_PERMISSION
|
||||
#define MENU_CONFIG_ENABLE_PERMISSION 1
|
||||
#endif
|
||||
|
||||
#ifndef MENU_CONFIG_ENABLE_PERSISTENCE
|
||||
#define MENU_CONFIG_ENABLE_PERSISTENCE 1
|
||||
#endif
|
||||
|
||||
#ifndef MENU_CONFIG_ENABLE_LANG
|
||||
#define MENU_CONFIG_ENABLE_LANG 1
|
||||
#endif
|
||||
|
||||
#ifndef MENU_CONFIG_ENABLE_DEBUG
|
||||
#define MENU_CONFIG_ENABLE_DEBUG 1
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // MENU_CONFIG_H
|
||||
316
src/core/menu_core.c
Normal file
316
src/core/menu_core.c
Normal file
@ -0,0 +1,316 @@
|
||||
#include "menu_core.h"
|
||||
#include "menu_event.h"
|
||||
#include "menu_stack.h"
|
||||
#include "menu_hash.h"
|
||||
#include "menu_param.h"
|
||||
#include <string.h>
|
||||
|
||||
// Internal helper to get node pointer from ID
|
||||
MenuNode* menu_core_get_node(MenuCoreCtx* ctx, MenuNodeId id) {
|
||||
if (!ctx || id == 0) return NULL;
|
||||
// Simple linear scan for now.
|
||||
// Optimization: Could use hash table if implemented to return index/pointer
|
||||
for (int i = 0; i < MENU_CONFIG_MAX_NODES; i++) {
|
||||
if (ctx->nodes[i].flags.is_registered && ctx->nodes[i].id == id) {
|
||||
return &ctx->nodes[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MenuErrCode menu_core_init(MenuCoreCtx* ctx) {
|
||||
if (!ctx) return MENU_ERR_INVALID_PARAM;
|
||||
memset(ctx, 0, sizeof(MenuCoreCtx));
|
||||
|
||||
menu_event_init(&ctx->event_queue);
|
||||
menu_stack_init(&ctx->stack);
|
||||
menu_hash_init(ctx);
|
||||
|
||||
ctx->is_initialized = true;
|
||||
ctx->current_state = MENU_STATE_INIT;
|
||||
ctx->free_node_count = MENU_CONFIG_MAX_NODES;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_core_register_node(MenuCoreCtx* ctx, const MenuNode* node, MenuNodeId* out_id) {
|
||||
if (!ctx || !node) return MENU_ERR_INVALID_PARAM;
|
||||
if (ctx->node_count >= MENU_CONFIG_MAX_NODES) return MENU_ERR_NO_MEM;
|
||||
|
||||
// Find a free slot
|
||||
int slot = -1;
|
||||
for (int i = 0; i < MENU_CONFIG_MAX_NODES; i++) {
|
||||
if (!ctx->nodes[i].flags.is_registered) {
|
||||
slot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (slot == -1) return MENU_ERR_NO_MEM;
|
||||
|
||||
MenuNode* new_node = &ctx->nodes[slot];
|
||||
*new_node = *node; // Copy data
|
||||
|
||||
// Assign ID if 0
|
||||
if (new_node->id == 0) {
|
||||
new_node->id = slot + 1; // 1-based ID
|
||||
}
|
||||
|
||||
// Check for ID conflict (simple check)
|
||||
MenuNode* existing = menu_core_get_node(ctx, new_node->id);
|
||||
if (existing && existing != new_node) {
|
||||
return MENU_ERR_INVALID_PARAM; // ID conflict
|
||||
}
|
||||
|
||||
// Mark as registered
|
||||
new_node->flags.is_registered = true;
|
||||
ctx->node_count++;
|
||||
ctx->free_node_count--;
|
||||
|
||||
// Link to parent
|
||||
if (new_node->parent_id != 0) {
|
||||
MenuNode* parent = menu_core_get_node(ctx, new_node->parent_id);
|
||||
if (parent) {
|
||||
if (parent->first_child_id == 0) {
|
||||
parent->first_child_id = new_node->id;
|
||||
} else {
|
||||
// Add to end of sibling list
|
||||
MenuNode* sibling = menu_core_get_node(ctx, parent->first_child_id);
|
||||
while (sibling && sibling->next_sibling_id != 0) {
|
||||
sibling = menu_core_get_node(ctx, sibling->next_sibling_id);
|
||||
}
|
||||
if (sibling) {
|
||||
sibling->next_sibling_id = new_node->id;
|
||||
new_node->prev_sibling_id = sibling->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add to hash
|
||||
menu_hash_insert(ctx, new_node->id);
|
||||
|
||||
if (out_id) *out_id = new_node->id;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_core_enter_node(MenuCoreCtx* ctx, MenuNodeId node_id) {
|
||||
if (!ctx) return MENU_ERR_INVALID_PARAM;
|
||||
|
||||
MenuNode* node = menu_core_get_node(ctx, node_id);
|
||||
if (!node) return MENU_ERR_NOT_FOUND;
|
||||
|
||||
// Permission check
|
||||
#if MENU_CONFIG_ENABLE_PERMISSION
|
||||
// TODO: Check permission
|
||||
#endif
|
||||
|
||||
// Call enter callback
|
||||
if (node->enter_cb) {
|
||||
node->enter_cb(node_id, ctx);
|
||||
}
|
||||
|
||||
ctx->current_node_id = node_id;
|
||||
node->flags.is_selected = true;
|
||||
|
||||
// Update path
|
||||
ctx->nav_path.depth = 0;
|
||||
MenuNodeId curr = node_id;
|
||||
MenuNodeId temp_path[MENU_CONFIG_STACK_DEPTH];
|
||||
int depth = 0;
|
||||
while(curr != 0 && depth < MENU_CONFIG_STACK_DEPTH) {
|
||||
temp_path[depth++] = curr;
|
||||
MenuNode* n = menu_core_get_node(ctx, curr);
|
||||
if (n) curr = n->parent_id;
|
||||
else break;
|
||||
}
|
||||
// Reverse
|
||||
for(int i=0; i<depth; i++) {
|
||||
ctx->nav_path.nodes[i] = temp_path[depth-1-i];
|
||||
}
|
||||
ctx->nav_path.depth = depth;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_core_exit_node(MenuCoreCtx* ctx, MenuNodeId node_id) {
|
||||
if (!ctx) return MENU_ERR_INVALID_PARAM;
|
||||
MenuNode* node = menu_core_get_node(ctx, node_id);
|
||||
if (node) {
|
||||
if (node->exit_cb) {
|
||||
node->exit_cb(node_id, ctx);
|
||||
}
|
||||
node->flags.is_selected = false;
|
||||
}
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_core_handle_event(MenuCoreCtx* ctx, const MenuEvent* event) {
|
||||
if (!ctx || !event) return MENU_ERR_INVALID_PARAM;
|
||||
|
||||
MenuNode* current = menu_core_get_node(ctx, ctx->current_node_id);
|
||||
// If no current node, maybe we need to enter root?
|
||||
if (!current) {
|
||||
// Try to find a root node (parent_id == 0)
|
||||
if (ctx->node_count > 0) {
|
||||
for(int i=0; i<MENU_CONFIG_MAX_NODES; i++) {
|
||||
if(ctx->nodes[i].flags.is_registered && ctx->nodes[i].parent_id == 0) {
|
||||
return menu_core_enter_node(ctx, ctx->nodes[i].id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return MENU_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
// Handle Parameter Edit State
|
||||
if (ctx->current_state == MENU_STATE_PARAM_EDIT) {
|
||||
if (current->param_id == 0) {
|
||||
ctx->current_state = MENU_STATE_NORMAL;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
const MenuParam* param = menu_param_get(current->param_id);
|
||||
if (!param) {
|
||||
ctx->current_state = MENU_STATE_NORMAL;
|
||||
return MENU_ERR_FAIL;
|
||||
}
|
||||
|
||||
switch (event->type) {
|
||||
case MENU_EVENT_KEY_UP:
|
||||
// Increase value
|
||||
// For simplicity assuming int types
|
||||
if (param->type >= MENU_PARAM_TYPE_INT8 && param->type <= MENU_PARAM_TYPE_UINT32) {
|
||||
int32_t val = menu_param_get_int(param->id);
|
||||
val += param->meta.num.step;
|
||||
if (val > param->meta.num.max) val = param->meta.num.min; // Wrap or clamp?
|
||||
menu_param_set_int(param->id, val);
|
||||
}
|
||||
break;
|
||||
case MENU_EVENT_KEY_DOWN:
|
||||
// Decrease value
|
||||
if (param->type >= MENU_PARAM_TYPE_INT8 && param->type <= MENU_PARAM_TYPE_UINT32) {
|
||||
int32_t val = menu_param_get_int(param->id);
|
||||
val -= param->meta.num.step;
|
||||
if (val < param->meta.num.min) val = param->meta.num.max;
|
||||
menu_param_set_int(param->id, val);
|
||||
}
|
||||
break;
|
||||
case MENU_EVENT_KEY_ENTER:
|
||||
// Confirm
|
||||
ctx->current_state = MENU_STATE_NORMAL;
|
||||
break;
|
||||
case MENU_EVENT_KEY_ESC:
|
||||
// Cancel (could revert if we stored old value)
|
||||
ctx->current_state = MENU_STATE_NORMAL;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
switch (event->type) {
|
||||
case MENU_EVENT_KEY_UP:
|
||||
if (current->prev_sibling_id != 0) {
|
||||
menu_core_exit_node(ctx, ctx->current_node_id);
|
||||
menu_core_enter_node(ctx, current->prev_sibling_id);
|
||||
} else {
|
||||
// Wrap around to last sibling
|
||||
MenuNode* last = current;
|
||||
while(last->next_sibling_id != 0) {
|
||||
MenuNode* next = menu_core_get_node(ctx, last->next_sibling_id);
|
||||
if(next) last = next; else break;
|
||||
}
|
||||
if (last != current) {
|
||||
menu_core_exit_node(ctx, ctx->current_node_id);
|
||||
menu_core_enter_node(ctx, last->id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MENU_EVENT_KEY_DOWN:
|
||||
if (current->next_sibling_id != 0) {
|
||||
menu_core_exit_node(ctx, ctx->current_node_id);
|
||||
menu_core_enter_node(ctx, current->next_sibling_id);
|
||||
} else {
|
||||
// Wrap to first sibling
|
||||
// First sibling is the first_child of parent
|
||||
if (current->parent_id != 0) {
|
||||
MenuNode* parent = menu_core_get_node(ctx, current->parent_id);
|
||||
if (parent && parent->first_child_id != 0 && parent->first_child_id != current->id) {
|
||||
menu_core_exit_node(ctx, ctx->current_node_id);
|
||||
menu_core_enter_node(ctx, parent->first_child_id);
|
||||
}
|
||||
} else {
|
||||
// Root level wrap
|
||||
// We need to find the first root node
|
||||
// But we don't track "first root".
|
||||
// We can traverse back using prev_sibling
|
||||
MenuNode* first = current;
|
||||
while(first->prev_sibling_id != 0) {
|
||||
MenuNode* prev = menu_core_get_node(ctx, first->prev_sibling_id);
|
||||
if(prev) first = prev; else break;
|
||||
}
|
||||
if (first != current) {
|
||||
menu_core_exit_node(ctx, ctx->current_node_id);
|
||||
menu_core_enter_node(ctx, first->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MENU_EVENT_KEY_ENTER:
|
||||
if (current->first_child_id != 0) {
|
||||
// Enter child
|
||||
menu_stack_push(&ctx->stack, ctx->current_node_id);
|
||||
// Note: We don't 'exit' the parent in terms of callback if we consider it "active in background"?
|
||||
// But normally in a menu system, you leave the parent screen and enter child screen.
|
||||
menu_core_exit_node(ctx, ctx->current_node_id);
|
||||
menu_core_enter_node(ctx, current->first_child_id);
|
||||
} else {
|
||||
// Leaf node action
|
||||
// Can trigger a custom event or just let the enter_cb handle it.
|
||||
// If we want to support "editing", we check if param_id is set.
|
||||
if (current->param_id != 0) {
|
||||
ctx->current_state = MENU_STATE_PARAM_EDIT;
|
||||
// Trigger redraw
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MENU_EVENT_KEY_ESC:
|
||||
if (!menu_stack_is_empty(&ctx->stack)) {
|
||||
MenuNodeId parent_id;
|
||||
menu_stack_pop(&ctx->stack, &parent_id);
|
||||
menu_core_exit_node(ctx, ctx->current_node_id);
|
||||
menu_core_enter_node(ctx, parent_id);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
void menu_core_loop(MenuCoreCtx* ctx, uint32_t tick) {
|
||||
if (!ctx || !ctx->is_initialized) return;
|
||||
|
||||
// Process events
|
||||
while (!menu_event_is_empty(&ctx->event_queue)) {
|
||||
MenuEvent evt;
|
||||
if (menu_event_get(&ctx->event_queue, &evt) == MENU_ERR_OK) {
|
||||
menu_core_handle_event(ctx, &evt);
|
||||
}
|
||||
}
|
||||
|
||||
// Render
|
||||
// Optimization: Add a render callback to the context or port
|
||||
// For now we rely on the current node's render_cb
|
||||
MenuNode* current = menu_core_get_node(ctx, ctx->current_node_id);
|
||||
if (current && current->render_cb) {
|
||||
current->render_cb(current->id, ctx);
|
||||
}
|
||||
}
|
||||
31
src/core/menu_core.h
Normal file
31
src/core/menu_core.h
Normal file
@ -0,0 +1,31 @@
|
||||
#ifndef MENU_CORE_H
|
||||
#define MENU_CORE_H
|
||||
|
||||
#include "menu_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Core initialization
|
||||
MenuErrCode menu_core_init(MenuCoreCtx* ctx);
|
||||
|
||||
// Node management
|
||||
MenuErrCode menu_core_register_node(MenuCoreCtx* ctx, const MenuNode* node, MenuNodeId* out_id);
|
||||
MenuNode* menu_core_get_node(MenuCoreCtx* ctx, MenuNodeId id);
|
||||
|
||||
// Main loop
|
||||
void menu_core_loop(MenuCoreCtx* ctx, uint32_t tick);
|
||||
|
||||
// State machine
|
||||
MenuErrCode menu_core_handle_event(MenuCoreCtx* ctx, const MenuEvent* event);
|
||||
|
||||
// Internal helpers
|
||||
MenuErrCode menu_core_enter_node(MenuCoreCtx* ctx, MenuNodeId node_id);
|
||||
MenuErrCode menu_core_exit_node(MenuCoreCtx* ctx, MenuNodeId node_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // MENU_CORE_H
|
||||
48
src/core/menu_event.c
Normal file
48
src/core/menu_event.c
Normal file
@ -0,0 +1,48 @@
|
||||
#include "menu_event.h"
|
||||
#include <string.h>
|
||||
|
||||
MenuErrCode menu_event_init(MenuEventQueue* queue) {
|
||||
if (!queue) return MENU_ERR_INVALID_PARAM;
|
||||
memset(queue, 0, sizeof(MenuEventQueue));
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_event_post(MenuEventQueue* queue, MenuEventType type, uint32_t param) {
|
||||
if (!queue) return MENU_ERR_INVALID_PARAM;
|
||||
|
||||
if (queue->count >= MENU_CONFIG_EVENT_QUEUE_LEN) {
|
||||
return MENU_ERR_QUEUE_FULL;
|
||||
}
|
||||
|
||||
MenuEvent* evt = &queue->events[queue->tail];
|
||||
evt->type = type;
|
||||
evt->param = param;
|
||||
// timestamp handling can be added here if we have system tick access,
|
||||
// for now left as 0 or handled by caller if passed in param (which is just 32bit)
|
||||
// or we can add a tick parameter to post_event.
|
||||
// For simplicity, we assume timestamp is not critical for now or set elsewhere.
|
||||
evt->timestamp = 0;
|
||||
|
||||
queue->tail = (queue->tail + 1) % MENU_CONFIG_EVENT_QUEUE_LEN;
|
||||
queue->count++;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_event_get(MenuEventQueue* queue, MenuEvent* event) {
|
||||
if (!queue || !event) return MENU_ERR_INVALID_PARAM;
|
||||
|
||||
if (queue->count == 0) {
|
||||
return MENU_ERR_QUEUE_EMPTY;
|
||||
}
|
||||
|
||||
*event = queue->events[queue->head];
|
||||
queue->head = (queue->head + 1) % MENU_CONFIG_EVENT_QUEUE_LEN;
|
||||
queue->count--;
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
bool menu_event_is_empty(const MenuEventQueue* queue) {
|
||||
return (queue == NULL) || (queue->count == 0);
|
||||
}
|
||||
19
src/core/menu_event.h
Normal file
19
src/core/menu_event.h
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef MENU_EVENT_H
|
||||
#define MENU_EVENT_H
|
||||
|
||||
#include "menu_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
MenuErrCode menu_event_init(MenuEventQueue* queue);
|
||||
MenuErrCode menu_event_post(MenuEventQueue* queue, MenuEventType type, uint32_t param);
|
||||
MenuErrCode menu_event_get(MenuEventQueue* queue, MenuEvent* event);
|
||||
bool menu_event_is_empty(const MenuEventQueue* queue);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // MENU_EVENT_H
|
||||
99
src/core/menu_hash.c
Normal file
99
src/core/menu_hash.c
Normal file
@ -0,0 +1,99 @@
|
||||
#include "menu_hash.h"
|
||||
#include "menu_core.h"
|
||||
#include <string.h>
|
||||
|
||||
MenuErrCode menu_hash_init(MenuCoreCtx* ctx) {
|
||||
if (!ctx) return MENU_ERR_INVALID_PARAM;
|
||||
memset(ctx->hash_table, 0, sizeof(ctx->hash_table));
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_hash_insert(MenuCoreCtx* ctx, MenuNodeId id) {
|
||||
if (!ctx) return MENU_ERR_INVALID_PARAM;
|
||||
|
||||
uint16_t index = id % MENU_CONFIG_HASH_TABLE_SIZE;
|
||||
MenuNodeId current = ctx->hash_table[index];
|
||||
|
||||
if (current == 0) {
|
||||
ctx->hash_table[index] = id;
|
||||
} else {
|
||||
// Collision handling: append to the end of the chain
|
||||
// Note: 0 is used as invalid ID/null pointer in this static array context
|
||||
// This implies valid node IDs must start from 1.
|
||||
|
||||
// Find the node structure for 'current'
|
||||
// This requires access to the node array which is in ctx
|
||||
// but we need to find the node index from the ID if ID != index
|
||||
// In this system, we usually assume ID corresponds to index if allocated sequentially,
|
||||
// but since we support arbitrary IDs (maybe?), we need to be careful.
|
||||
// HOWEVER, the design says "Static node pool", so usually we just iterate.
|
||||
// BUT wait, if we are building a hash table, we want O(1) access.
|
||||
// Let's assume ID is NOT the index in the array, but a unique handle.
|
||||
// But we need to find the MenuNode* from ID to set hash_next_id.
|
||||
|
||||
// Since we are inside menu_hash_insert, we probably just added the node
|
||||
// so we might know where it is, but this function takes ID.
|
||||
// We need a way to look up the MenuNode* from ID to set the next pointer.
|
||||
// If we can't find it efficiently, this hash insert is slow.
|
||||
|
||||
// Actually, looking at `MenuCoreCtx`, `nodes` is an array.
|
||||
// Does `nodes[i].id` == i?
|
||||
// If the user provides arbitrary IDs, we need to scan the array to find the node
|
||||
// to set its `hash_next_id`.
|
||||
// That defeats the purpose of the hash table if insertion is O(N).
|
||||
// But insertion happens only at init. Lookup is frequent.
|
||||
|
||||
// Let's implement finding the node by scanning for insertion.
|
||||
MenuNode* prev_node = NULL;
|
||||
// Find the head of the chain
|
||||
for (int i = 0; i < ctx->node_count; i++) {
|
||||
if (ctx->nodes[i].id == current) {
|
||||
prev_node = &ctx->nodes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!prev_node) return MENU_ERR_NOT_FOUND; // Should not happen
|
||||
|
||||
while (prev_node->hash_next_id != 0) {
|
||||
MenuNodeId next_id = prev_node->hash_next_id;
|
||||
bool found = false;
|
||||
for (int i = 0; i < ctx->node_count; i++) {
|
||||
if (ctx->nodes[i].id == next_id) {
|
||||
prev_node = &ctx->nodes[i];
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) return MENU_ERR_FAIL; // Broken chain
|
||||
}
|
||||
|
||||
prev_node->hash_next_id = id;
|
||||
}
|
||||
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuNodeId menu_hash_find(MenuCoreCtx* ctx, MenuNodeId id) {
|
||||
if (!ctx) return 0;
|
||||
|
||||
uint16_t index = id % MENU_CONFIG_HASH_TABLE_SIZE;
|
||||
MenuNodeId current = ctx->hash_table[index];
|
||||
|
||||
while (current != 0) {
|
||||
if (current == id) return current;
|
||||
|
||||
// Move to next
|
||||
bool found = false;
|
||||
for (int i = 0; i < ctx->node_count; i++) {
|
||||
if (ctx->nodes[i].id == current) {
|
||||
current = ctx->nodes[i].hash_next_id;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) break;
|
||||
}
|
||||
|
||||
return 0; // Not found
|
||||
}
|
||||
18
src/core/menu_hash.h
Normal file
18
src/core/menu_hash.h
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef MENU_HASH_H
|
||||
#define MENU_HASH_H
|
||||
|
||||
#include "menu_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
MenuErrCode menu_hash_init(MenuCoreCtx* ctx);
|
||||
MenuErrCode menu_hash_insert(MenuCoreCtx* ctx, MenuNodeId id);
|
||||
MenuNodeId menu_hash_find(MenuCoreCtx* ctx, MenuNodeId id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // MENU_HASH_H
|
||||
50
src/core/menu_stack.c
Normal file
50
src/core/menu_stack.c
Normal file
@ -0,0 +1,50 @@
|
||||
#include "menu_stack.h"
|
||||
#include <string.h>
|
||||
|
||||
MenuErrCode menu_stack_init(MenuStack* stack) {
|
||||
if (!stack) return MENU_ERR_INVALID_PARAM;
|
||||
memset(stack, 0, sizeof(MenuStack));
|
||||
stack->top = 0;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_stack_push(MenuStack* stack, MenuNodeId id) {
|
||||
if (!stack) return MENU_ERR_INVALID_PARAM;
|
||||
if (stack->top >= MENU_CONFIG_STACK_DEPTH) {
|
||||
return MENU_ERR_STACK_OVERFLOW;
|
||||
}
|
||||
stack->path[stack->top++] = id;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_stack_pop(MenuStack* stack, MenuNodeId* id) {
|
||||
if (!stack) return MENU_ERR_INVALID_PARAM;
|
||||
if (stack->top == 0) {
|
||||
return MENU_ERR_STACK_UNDERFLOW;
|
||||
}
|
||||
if (id) {
|
||||
*id = stack->path[--stack->top];
|
||||
} else {
|
||||
stack->top--;
|
||||
}
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_stack_peek(const MenuStack* stack, MenuNodeId* id) {
|
||||
if (!stack || !id) return MENU_ERR_INVALID_PARAM;
|
||||
if (stack->top == 0) {
|
||||
return MENU_ERR_STACK_UNDERFLOW;
|
||||
}
|
||||
*id = stack->path[stack->top - 1];
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
bool menu_stack_is_empty(const MenuStack* stack) {
|
||||
return (stack == NULL) || (stack->top == 0);
|
||||
}
|
||||
|
||||
void menu_stack_clear(MenuStack* stack) {
|
||||
if (stack) {
|
||||
stack->top = 0;
|
||||
}
|
||||
}
|
||||
21
src/core/menu_stack.h
Normal file
21
src/core/menu_stack.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef MENU_STACK_H
|
||||
#define MENU_STACK_H
|
||||
|
||||
#include "menu_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
MenuErrCode menu_stack_init(MenuStack* stack);
|
||||
MenuErrCode menu_stack_push(MenuStack* stack, MenuNodeId id);
|
||||
MenuErrCode menu_stack_pop(MenuStack* stack, MenuNodeId* id);
|
||||
MenuErrCode menu_stack_peek(const MenuStack* stack, MenuNodeId* id);
|
||||
bool menu_stack_is_empty(const MenuStack* stack);
|
||||
void menu_stack_clear(MenuStack* stack);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // MENU_STACK_H
|
||||
153
src/core/menu_types.h
Normal file
153
src/core/menu_types.h
Normal file
@ -0,0 +1,153 @@
|
||||
#ifndef MENU_TYPES_H
|
||||
#define MENU_TYPES_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include "menu_config.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Forward declaration
|
||||
struct MenuCoreCtx;
|
||||
|
||||
// Type definitions
|
||||
typedef uint16_t MenuNodeId;
|
||||
typedef uint8_t MenuPermissionLevel;
|
||||
|
||||
// Error codes
|
||||
typedef enum {
|
||||
MENU_ERR_OK = 0,
|
||||
MENU_ERR_FAIL,
|
||||
MENU_ERR_INVALID_PARAM,
|
||||
MENU_ERR_NO_MEM,
|
||||
MENU_ERR_NOT_FOUND,
|
||||
MENU_ERR_PERMISSION_DENIED,
|
||||
MENU_ERR_QUEUE_FULL,
|
||||
MENU_ERR_QUEUE_EMPTY,
|
||||
MENU_ERR_STACK_OVERFLOW,
|
||||
MENU_ERR_STACK_UNDERFLOW,
|
||||
MENU_ERR_NOT_INIT,
|
||||
} MenuErrCode;
|
||||
|
||||
// Menu States
|
||||
typedef enum {
|
||||
MENU_STATE_INIT = 0,
|
||||
MENU_STATE_NORMAL,
|
||||
MENU_STATE_PARAM_EDIT,
|
||||
MENU_STATE_CONFIRM,
|
||||
MENU_STATE_ERROR,
|
||||
} MenuState;
|
||||
|
||||
// Menu Events
|
||||
typedef enum {
|
||||
MENU_EVENT_NONE = 0,
|
||||
MENU_EVENT_KEY_UP,
|
||||
MENU_EVENT_KEY_DOWN,
|
||||
MENU_EVENT_KEY_ENTER,
|
||||
MENU_EVENT_KEY_ESC,
|
||||
MENU_EVENT_TIMEOUT,
|
||||
MENU_EVENT_RENDER, // Optimization: Render event
|
||||
MENU_EVENT_CUSTOM_BASE = 10,
|
||||
} MenuEventType;
|
||||
|
||||
// Callback function types
|
||||
typedef MenuErrCode (*MenuCallback)(MenuNodeId node_id, struct MenuCoreCtx* ctx);
|
||||
|
||||
// Menu Node Structure
|
||||
typedef struct MenuNode {
|
||||
MenuNodeId id;
|
||||
MenuNodeId parent_id;
|
||||
const char* name; // Or language key
|
||||
MenuCallback enter_cb;
|
||||
MenuCallback exit_cb;
|
||||
MenuCallback render_cb; // Optimization: Custom render callback per node
|
||||
|
||||
MenuNodeId first_child_id;
|
||||
MenuNodeId next_sibling_id;
|
||||
MenuNodeId prev_sibling_id;
|
||||
MenuNodeId hash_next_id;
|
||||
|
||||
struct {
|
||||
bool is_registered : 1;
|
||||
bool is_selected : 1;
|
||||
bool is_visible : 1;
|
||||
bool is_enabled : 1;
|
||||
unsigned int reserved : 4;
|
||||
} flags;
|
||||
|
||||
MenuPermissionLevel permission_level;
|
||||
uint16_t param_id;
|
||||
void* user_data; // Optimization: User context
|
||||
} MenuNode;
|
||||
|
||||
// Navigation Stack
|
||||
typedef struct {
|
||||
MenuNodeId path[MENU_CONFIG_STACK_DEPTH];
|
||||
uint8_t top;
|
||||
} MenuStack;
|
||||
|
||||
// Event structure
|
||||
typedef struct {
|
||||
MenuEventType type;
|
||||
uint32_t param;
|
||||
uint32_t timestamp;
|
||||
} MenuEvent;
|
||||
|
||||
// Event Queue
|
||||
typedef struct {
|
||||
MenuEvent events[MENU_CONFIG_EVENT_QUEUE_LEN];
|
||||
uint8_t head;
|
||||
uint8_t tail;
|
||||
uint8_t count;
|
||||
} MenuEventQueue;
|
||||
|
||||
// Navigation Path (breadcrumbs for display)
|
||||
typedef struct {
|
||||
MenuNodeId nodes[MENU_CONFIG_STACK_DEPTH];
|
||||
uint8_t depth;
|
||||
} MenuNavPath;
|
||||
|
||||
// Core Context
|
||||
typedef struct MenuCoreCtx {
|
||||
MenuNode nodes[MENU_CONFIG_MAX_NODES];
|
||||
MenuNodeId hash_table[MENU_CONFIG_HASH_TABLE_SIZE];
|
||||
MenuStack stack;
|
||||
MenuNavPath nav_path;
|
||||
MenuEventQueue event_queue;
|
||||
|
||||
MenuNodeId current_node_id;
|
||||
MenuState current_state;
|
||||
uint32_t last_refresh_tick;
|
||||
bool is_initialized;
|
||||
|
||||
MenuErrCode error_code;
|
||||
const char* error_msg;
|
||||
|
||||
uint16_t node_count;
|
||||
uint16_t free_node_count;
|
||||
|
||||
// Permission module data
|
||||
struct {
|
||||
uint8_t current_role_id;
|
||||
} permission;
|
||||
|
||||
// Persistence module data
|
||||
struct {
|
||||
bool dirty;
|
||||
} persistence;
|
||||
|
||||
// Language module data
|
||||
struct {
|
||||
uint8_t current_lang_id;
|
||||
} lang;
|
||||
|
||||
} MenuCoreCtx;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // MENU_TYPES_H
|
||||
28
src/lang/menu_lang.c
Normal file
28
src/lang/menu_lang.c
Normal file
@ -0,0 +1,28 @@
|
||||
#include "menu_lang.h"
|
||||
#include <string.h>
|
||||
|
||||
static MenuLangID g_curr_lang = MENU_LANG_EN;
|
||||
|
||||
void menu_lang_set(MenuLangID lang_id) {
|
||||
if (lang_id < MENU_LANG_MAX) {
|
||||
g_curr_lang = lang_id;
|
||||
}
|
||||
}
|
||||
|
||||
MenuLangID menu_lang_get(void) {
|
||||
return g_curr_lang;
|
||||
}
|
||||
|
||||
const char* menu_lang_get_str(const char* key) {
|
||||
// Simple placeholder. In real implementation, look up in table.
|
||||
// If key starts with '$', it's a key, else return as is.
|
||||
if (!key) return "";
|
||||
if (key[0] == '$') {
|
||||
// Mock lookup
|
||||
if (strcmp(key, "$MAIN") == 0) return (g_curr_lang == MENU_LANG_CN) ? "主菜单" : "Main Menu";
|
||||
if (strcmp(key, "$SETTING") == 0) return (g_curr_lang == MENU_LANG_CN) ? "设置" : "Settings";
|
||||
if (strcmp(key, "$INFO") == 0) return (g_curr_lang == MENU_LANG_CN) ? "信息" : "Info";
|
||||
return key + 1; // Return key without $
|
||||
}
|
||||
return key;
|
||||
}
|
||||
27
src/lang/menu_lang.h
Normal file
27
src/lang/menu_lang.h
Normal file
@ -0,0 +1,27 @@
|
||||
#ifndef MENU_LANG_H
|
||||
#define MENU_LANG_H
|
||||
|
||||
#include "../core/menu_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
MENU_LANG_EN = 0,
|
||||
MENU_LANG_CN,
|
||||
MENU_LANG_MAX
|
||||
} MenuLangID;
|
||||
|
||||
// Set current language
|
||||
void menu_lang_set(MenuLangID lang_id);
|
||||
MenuLangID menu_lang_get(void);
|
||||
|
||||
// Get string
|
||||
const char* menu_lang_get_str(const char* key);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // MENU_LANG_H
|
||||
96
src/param/menu_param.c
Normal file
96
src/param/menu_param.c
Normal file
@ -0,0 +1,96 @@
|
||||
#include "menu_param.h"
|
||||
#include <string.h>
|
||||
|
||||
#define MENU_MAX_PARAMS 32
|
||||
|
||||
static MenuParam g_params[MENU_MAX_PARAMS];
|
||||
static uint16_t g_param_count = 0;
|
||||
|
||||
MenuErrCode menu_param_init(void) {
|
||||
memset(g_params, 0, sizeof(g_params));
|
||||
g_param_count = 0;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_param_register(const MenuParam* param) {
|
||||
if (!param) return MENU_ERR_INVALID_PARAM;
|
||||
if (g_param_count >= MENU_MAX_PARAMS) return MENU_ERR_NO_MEM;
|
||||
|
||||
// Check ID conflict
|
||||
for (int i = 0; i < g_param_count; i++) {
|
||||
if (g_params[i].id == param->id) {
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
}
|
||||
|
||||
g_params[g_param_count++] = *param;
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
const MenuParam* menu_param_get(uint16_t id) {
|
||||
for (int i = 0; i < g_param_count; i++) {
|
||||
if (g_params[i].id == id) {
|
||||
return &g_params[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MenuErrCode menu_param_set_int(uint16_t id, int32_t val) {
|
||||
const MenuParam* p = menu_param_get(id);
|
||||
if (!p || p->read_only || !p->value_ptr) return MENU_ERR_INVALID_PARAM;
|
||||
|
||||
switch (p->type) {
|
||||
case MENU_PARAM_TYPE_BOOL:
|
||||
*(bool*)p->value_ptr = (val != 0);
|
||||
break;
|
||||
case MENU_PARAM_TYPE_INT8:
|
||||
if (val < p->meta.num.min || val > p->meta.num.max) return MENU_ERR_INVALID_PARAM;
|
||||
*(int8_t*)p->value_ptr = (int8_t)val;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_UINT8:
|
||||
if (val < p->meta.num.min || val > p->meta.num.max) return MENU_ERR_INVALID_PARAM;
|
||||
*(uint8_t*)p->value_ptr = (uint8_t)val;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_INT16:
|
||||
if (val < p->meta.num.min || val > p->meta.num.max) return MENU_ERR_INVALID_PARAM;
|
||||
*(int16_t*)p->value_ptr = (int16_t)val;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_UINT16:
|
||||
if (val < p->meta.num.min || val > p->meta.num.max) return MENU_ERR_INVALID_PARAM;
|
||||
*(uint16_t*)p->value_ptr = (uint16_t)val;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_INT32:
|
||||
if (val < p->meta.num.min || val > p->meta.num.max) return MENU_ERR_INVALID_PARAM;
|
||||
*(int32_t*)p->value_ptr = (int32_t)val;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_UINT32:
|
||||
if (val < p->meta.num.min || val > p->meta.num.max) return MENU_ERR_INVALID_PARAM;
|
||||
*(uint32_t*)p->value_ptr = (uint32_t)val;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_ENUM:
|
||||
if (val < 0 || val >= p->meta.enum_meta.count) return MENU_ERR_INVALID_PARAM;
|
||||
*(uint8_t*)p->value_ptr = (uint8_t)val; // Assume enum stored as uint8
|
||||
break;
|
||||
default:
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
return MENU_ERR_OK;
|
||||
}
|
||||
|
||||
int32_t menu_param_get_int(uint16_t id) {
|
||||
const MenuParam* p = menu_param_get(id);
|
||||
if (!p || !p->value_ptr) return 0;
|
||||
|
||||
switch (p->type) {
|
||||
case MENU_PARAM_TYPE_BOOL: return *(bool*)p->value_ptr;
|
||||
case MENU_PARAM_TYPE_INT8: return *(int8_t*)p->value_ptr;
|
||||
case MENU_PARAM_TYPE_UINT8: return *(uint8_t*)p->value_ptr;
|
||||
case MENU_PARAM_TYPE_INT16: return *(int16_t*)p->value_ptr;
|
||||
case MENU_PARAM_TYPE_UINT16: return *(uint16_t*)p->value_ptr;
|
||||
case MENU_PARAM_TYPE_INT32: return *(int32_t*)p->value_ptr;
|
||||
case MENU_PARAM_TYPE_UINT32: return *(uint32_t*)p->value_ptr;
|
||||
case MENU_PARAM_TYPE_ENUM: return *(uint8_t*)p->value_ptr;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
63
src/param/menu_param.h
Normal file
63
src/param/menu_param.h
Normal file
@ -0,0 +1,63 @@
|
||||
#ifndef MENU_PARAM_H
|
||||
#define MENU_PARAM_H
|
||||
|
||||
#include "../core/menu_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
MENU_PARAM_TYPE_NONE = 0,
|
||||
MENU_PARAM_TYPE_BOOL,
|
||||
MENU_PARAM_TYPE_INT8,
|
||||
MENU_PARAM_TYPE_UINT8,
|
||||
MENU_PARAM_TYPE_INT16,
|
||||
MENU_PARAM_TYPE_UINT16,
|
||||
MENU_PARAM_TYPE_INT32,
|
||||
MENU_PARAM_TYPE_UINT32,
|
||||
MENU_PARAM_TYPE_FLOAT,
|
||||
MENU_PARAM_TYPE_STRING,
|
||||
MENU_PARAM_TYPE_ENUM,
|
||||
} MenuParamType;
|
||||
|
||||
typedef struct {
|
||||
uint16_t id;
|
||||
MenuParamType type;
|
||||
const char* name;
|
||||
void* value_ptr; // Pointer to actual variable
|
||||
union {
|
||||
struct {
|
||||
int32_t min;
|
||||
int32_t max;
|
||||
int32_t step;
|
||||
} num;
|
||||
struct {
|
||||
const char** options;
|
||||
uint8_t count;
|
||||
} enum_meta;
|
||||
struct {
|
||||
uint16_t max_len;
|
||||
} str_meta;
|
||||
} meta;
|
||||
bool read_only;
|
||||
} MenuParam;
|
||||
|
||||
// Initialize param system
|
||||
MenuErrCode menu_param_init(void);
|
||||
|
||||
// Register a parameter
|
||||
MenuErrCode menu_param_register(const MenuParam* param);
|
||||
|
||||
// Get parameter by ID
|
||||
const MenuParam* menu_param_get(uint16_t id);
|
||||
|
||||
// Set value (helper)
|
||||
MenuErrCode menu_param_set_int(uint16_t id, int32_t val);
|
||||
int32_t menu_param_get_int(uint16_t id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // MENU_PARAM_H
|
||||
@ -1,50 +0,0 @@
|
||||
def generate_menu_nodes_source(self):
|
||||
"""生成菜单节点源文件"""
|
||||
menu_src_file = os.path.join(self.output_dir, f"{self.source_prefix}nodes.c")
|
||||
|
||||
with open(menu_src_file, 'w', encoding='utf-8') as f:
|
||||
f.write("/**\n")
|
||||
f.write(" **********************************************************************************************************************\n")
|
||||
f.write(" * @file menu_nodes.c\n")
|
||||
f.write(" * @brief 电磁启动器菜单节点实现\n")
|
||||
f.write(" * @author Code Generator\n")
|
||||
f.write(" * @date 2025-12-22 12:00:00\n")
|
||||
f.write(" **********************************************************************************************************************\n")
|
||||
f.write(" */\n\n")
|
||||
f.write("#include \"menu_nodes.h\"\n\n")
|
||||
|
||||
# 生成菜单节点数组
|
||||
f.write("/* 菜单节点数组定义 */\n")
|
||||
f.write("const MenuNode g_menu_nodes[] = {\n")
|
||||
|
||||
for i, node in enumerate(self.nodes):
|
||||
f.write(" {\n")
|
||||
f.write(f" .id = {node['id']},\n")
|
||||
f.write(f" .parent_id = {node['parent_id']},\n")
|
||||
f.write(f" .name_en = \"{node['name']['en']}",\n")
|
||||
f.write(f" .name_cn = \"{node['name']['cn']}",\n")
|
||||
f.write(f" .id_name = \"{node['id_name']}",\n")
|
||||
f.write(f" .enter_cb = {node.get('enter_cb', 'NULL')},\n")
|
||||
f.write(f" .display_cb = {node.get('display_cb', 'NULL')},\n")
|
||||
f.write(f" .action_cb = {node.get('action_cb', 'NULL')},\n")
|
||||
f.write(f" .visible_cb = {node.get('visible_cb', 'NULL')},\n")
|
||||
f.write(f" .permission_level = {node.get('permission_level', 0)},\n")
|
||||
f.write(f" .confirm_required = {str(node.get('confirm_required', False)).lower()},\n")
|
||||
f.write(f" .bind_param_id = {node.get('bind_param_id', 0)},\n")
|
||||
f.write(f" .menu_item_type = {node['c_code'].get('menu_item_type', 'SUB_MENU')}\n")
|
||||
|
||||
# 最后一个元素不需要逗号
|
||||
if i == len(self.nodes) - 1:
|
||||
f.write(" }\n")
|
||||
else:
|
||||
f.write(" },\n")
|
||||
|
||||
f.write("};
|
||||
|
||||
")
|
||||
|
||||
# 生成菜单节点数量
|
||||
f.write("/* 菜单节点数量 */\n")
|
||||
f.write("const uint32_t g_menu_node_count = sizeof(g_menu_nodes) / sizeof(g_menu_nodes[0]);\n\n")
|
||||
|
||||
print(f"菜单节点源文件生成成功: {menu_src_file}")
|
||||
Reference in New Issue
Block a user