初始化版本

This commit is contained in:
冯佳
2025-12-23 08:29:44 +08:00
parent 0ec8a5b380
commit cc9f363ff8
22 changed files with 1370 additions and 50 deletions

49
src/core/menu_config.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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