diff --git a/api/menu_api.c b/api/menu_api.c index e84744d..4e22cb8 100644 --- a/api/menu_api.c +++ b/api/menu_api.c @@ -58,6 +58,36 @@ MenuNodeId menu_register_node_ex( return 0; } +MenuErrCode menu_register_nodes( + const MenuNode* nodes, + size_t count, + MenuNodeId* out_ids +) { + if (!nodes || count == 0) return MENU_ERR_INVALID_PARAM; + + for (size_t i = 0; i < count; i++) { + MenuNode node = nodes[i]; + MenuNodeId new_id = 0; + + node.flags.is_enabled = true; + node.flags.is_visible = true; + node.flags.is_registered = false; + node.param_id = node.param_id ? node.param_id : 0; + node.permission_level = node.permission_level ? node.permission_level : 0; + + MenuErrCode err = menu_core_register_node(&g_menu_ctx, &node, &new_id); + if (err != MENU_ERR_OK) { + return err; + } + + if (out_ids) { + out_ids[i] = new_id; + } + } + + return MENU_ERR_OK; +} + MenuErrCode menu_post_event(MenuEventType type, uint32_t param) { return menu_event_post(&g_menu_ctx.event_queue, type, param); } @@ -113,3 +143,74 @@ MenuErrCode menu_node_bind_param(MenuNodeId node_id, uint16_t param_id) { node->param_id = param_id; return MENU_ERR_OK; } + +// Permission management functions +MenuErrCode menu_permission_register_role(uint8_t role_id, const char* name, MenuPermissionLevel level) { + if (!name) return MENU_ERR_INVALID_PARAM; + if (g_menu_ctx.permission.role_count >= 8) return MENU_ERR_NO_MEM; + + for (uint8_t i = 0; i < g_menu_ctx.permission.role_count; i++) { + if (g_menu_ctx.permission.roles[i].id == role_id) { + return MENU_ERR_FAIL; + } + } + + MenuRole* role = &g_menu_ctx.permission.roles[g_menu_ctx.permission.role_count]; + role->id = role_id; + role->name = name; + role->level = level; + g_menu_ctx.permission.role_count++; + + return MENU_ERR_OK; +} + +MenuErrCode menu_permission_set_current_role(uint8_t role_id) { + g_menu_ctx.permission.current_role_id = role_id; + return MENU_ERR_OK; +} + +MenuErrCode menu_permission_update_node_level(MenuNodeId node_id, MenuPermissionLevel level) { + MenuNode* node = menu_core_get_node(&g_menu_ctx, node_id); + if (!node) return MENU_ERR_NOT_FOUND; + + node->permission_level = level; + return MENU_ERR_OK; +} + +bool menu_permission_check_node_access(MenuNodeId node_id) { + MenuNode* node = menu_core_get_node(&g_menu_ctx, node_id); + if (!node) return false; + + #if MENU_CONFIG_ENABLE_PERMISSION + MenuRole* role_node = NULL; + for (uint8_t i = 0; i < g_menu_ctx.permission.role_count; i++) { + if (g_menu_ctx.permission.roles[i].id == g_menu_ctx.permission.current_role_id) { + role_node = &g_menu_ctx.permission.roles[i]; + break; + } + } + + if (!role_node || role_node->level < node->permission_level) { + return false; + } + #endif + + return true; +} + +// Persistence functions +MenuErrCode menu_persistence_save(void) { + return menu_core_save_state(&g_menu_ctx); +} + +MenuErrCode menu_persistence_load(void) { + return menu_core_load_state(&g_menu_ctx); +} + +MenuErrCode menu_refresh(void) { + MenuErrCode err = menu_post_event(MENU_EVENT_RENDER, 0); + if (err != MENU_ERR_OK) { + return err; + } + return MENU_ERR_OK; +} diff --git a/api/menu_api.h b/api/menu_api.h index d88c31b..514c90e 100644 --- a/api/menu_api.h +++ b/api/menu_api.h @@ -34,6 +34,13 @@ MenuNodeId menu_register_node_ex( void* user_data ); +// Batch node registration +MenuErrCode menu_register_nodes( + const MenuNode* nodes, + size_t count, + MenuNodeId* out_ids +); + // Event Handling MenuErrCode menu_post_event(MenuEventType type, uint32_t param); @@ -44,6 +51,19 @@ MenuErrCode menu_back(void); // Parameter Binding MenuErrCode menu_node_bind_param(MenuNodeId node_id, uint16_t param_id); +// Permission Management +MenuErrCode menu_permission_register_role(uint8_t role_id, const char* name, MenuPermissionLevel level); +MenuErrCode menu_permission_set_current_role(uint8_t role_id); +MenuErrCode menu_permission_update_node_level(MenuNodeId node_id, MenuPermissionLevel level); +bool menu_permission_check_node_access(MenuNodeId node_id); + +// Persistence Management +MenuErrCode menu_persistence_save(void); +MenuErrCode menu_persistence_load(void); + +// Dynamic menu updates +MenuErrCode menu_refresh(void); + #ifdef __cplusplus } #endif diff --git a/examples/demo.c b/examples/demo.c index 1855fbf..12d9896 100644 --- a/examples/demo.c +++ b/examples/demo.c @@ -8,7 +8,7 @@ // Globals int32_t g_volume = 50; int32_t g_brightness = 80; -uint8_t g_language = 0; // 0=EN, 1=CN +uint8_t g_language = 1; // 0=EN, 1=CN // Callbacks MenuErrCode on_enter_main(MenuNodeId id, struct MenuCoreCtx* ctx) { diff --git a/src/core/menu_core.c b/src/core/menu_core.c index 86b83dd..a0e7096 100644 --- a/src/core/menu_core.c +++ b/src/core/menu_core.c @@ -8,14 +8,7 @@ // 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; + return menu_hash_find(ctx, id); } MenuErrCode menu_core_init(MenuCoreCtx* ctx) { @@ -86,15 +79,91 @@ MenuErrCode menu_core_register_node(MenuCoreCtx* ctx, const MenuNode* node, Menu } } } - + // Add to hash - menu_hash_insert(ctx, new_node->id); + menu_hash_insert(ctx, new_node->id, slot); if (out_id) *out_id = new_node->id; return MENU_ERR_OK; } +MenuErrCode menu_core_unregister_node(MenuCoreCtx* ctx, MenuNodeId id) { + if (!ctx || id == 0) return MENU_ERR_INVALID_PARAM; + + MenuNode* node = menu_core_get_node(ctx, id); + if (!node) return MENU_ERR_NOT_FOUND; + + // Find slot index + int slot = -1; + for (int i = 0; i < MENU_CONFIG_MAX_NODES; i++) { + if (ctx->nodes[i].flags.is_registered && ctx->nodes[i].id == id) { + slot = i; + break; + } + } + + if (slot == -1) return MENU_ERR_NOT_FOUND; + + // Update parent-child relationship + if (node->parent_id != 0) { + MenuNode* parent = menu_core_get_node(ctx, node->parent_id); + if (parent) { + // Check if this is the first child + if (parent->first_child_id == id) { + parent->first_child_id = node->next_sibling_id; + } else { + // Find previous sibling to update links + MenuNode* prev_sibling = menu_core_get_node(ctx, node->prev_sibling_id); + if (prev_sibling) { + prev_sibling->next_sibling_id = node->next_sibling_id; + } + } + } + } + + // Update next sibling's prev_sibling_id + if (node->next_sibling_id != 0) { + MenuNode* next_sibling = menu_core_get_node(ctx, node->next_sibling_id); + if (next_sibling) { + next_sibling->prev_sibling_id = node->prev_sibling_id; + } + } + + // Clear node data + ctx->nodes[slot].flags.is_registered = false; + ctx->nodes[slot].id = 0; + ctx->nodes[slot].parent_id = 0; + ctx->nodes[slot].first_child_id = 0; + ctx->nodes[slot].next_sibling_id = 0; + ctx->nodes[slot].prev_sibling_id = 0; + ctx->nodes[slot].hash_next_id = 0; + ctx->nodes[slot].flags.is_selected = false; + ctx->nodes[slot].flags.is_visible = false; + ctx->nodes[slot].flags.is_enabled = false; + ctx->nodes[slot].permission_level = 0; + ctx->nodes[slot].param_id = 0; + ctx->nodes[slot].user_data = NULL; + ctx->nodes[slot].name = NULL; + ctx->nodes[slot].enter_cb = NULL; + ctx->nodes[slot].exit_cb = NULL; + ctx->nodes[slot].render_cb = NULL; + + // Update counts + ctx->node_count--; + ctx->free_node_count++; + + // Rebuild hash table since we can't easily remove from it + menu_hash_init(ctx); + for (int i = 0; i < MENU_CONFIG_MAX_NODES; i++) { + if (ctx->nodes[i].flags.is_registered) { + menu_hash_insert(ctx, ctx->nodes[i].id, i); + } + } + + return MENU_ERR_OK; +} + MenuErrCode menu_core_enter_node(MenuCoreCtx* ctx, MenuNodeId node_id) { if (!ctx) return MENU_ERR_INVALID_PARAM; @@ -103,7 +172,16 @@ MenuErrCode menu_core_enter_node(MenuCoreCtx* ctx, MenuNodeId node_id) { // Permission check #if MENU_CONFIG_ENABLE_PERMISSION - // TODO: Check permission + MenuRole* role_node = NULL; + for (uint8_t i = 0; i < ctx->permission.role_count; i++) { + if (ctx->permission.roles[i].id == ctx->permission.current_role_id) { + role_node = &ctx->permission.roles[i]; + break; + } + } + if (!role_node || role_node->level < node->permission_level) { + return MENU_ERR_PERMISSION_DENIED; + } #endif // Call enter callback @@ -295,6 +373,46 @@ MenuErrCode menu_core_handle_event(MenuCoreCtx* ctx, const MenuEvent* event) { return MENU_ERR_OK; } +static uint8_t calculate_checksum(const MenuPersistenceData* data) { + uint8_t checksum = 0; + const uint8_t* bytes = (const uint8_t*)data; + size_t len = sizeof(MenuPersistenceData) - sizeof(data->checksum); + for (size_t i = 0; i < len; i++) { + checksum ^= bytes[i]; + } + return checksum; +} + +MenuErrCode menu_core_save_state(MenuCoreCtx* ctx) { + if (!ctx) return MENU_ERR_INVALID_PARAM; + + MenuPersistenceData data = { + .current_node_id = ctx->current_node_id, + .current_state = ctx->current_state, + .nav_path = ctx->nav_path, + .stack = ctx->stack, + .timestamp = 0, + .checksum = 0 + }; + + data.checksum = calculate_checksum(&data); + + // TODO: Implement actual storage write logic here + // This would typically write to EEPROM, Flash, or other non-volatile storage + // For now, we'll just mark as not dirty + ctx->persistence.dirty = false; + return MENU_ERR_OK; +} + +MenuErrCode menu_core_load_state(MenuCoreCtx* ctx) { + if (!ctx) return MENU_ERR_INVALID_PARAM; + + // TODO: Implement actual storage read logic here + // This would typically read from EEPROM, Flash, or other non-volatile storage + // For now, we'll just return OK + return MENU_ERR_OK; +} + void menu_core_loop(MenuCoreCtx* ctx, uint32_t tick) { if (!ctx || !ctx->is_initialized) return; @@ -303,14 +421,20 @@ void menu_core_loop(MenuCoreCtx* ctx, uint32_t tick) { MenuEvent evt; if (menu_event_get(&ctx->event_queue, &evt) == MENU_ERR_OK) { menu_core_handle_event(ctx, &evt); + ctx->persistence.dirty = true; } } // 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); } + + // Auto-save if dirty + #if MENU_CONFIG_ENABLE_PERSISTENCE + if (ctx->persistence.dirty) { + menu_core_save_state(ctx); + } + #endif } diff --git a/src/core/menu_core.h b/src/core/menu_core.h index 157ad5a..4bdde41 100644 --- a/src/core/menu_core.h +++ b/src/core/menu_core.h @@ -13,6 +13,7 @@ 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); +MenuErrCode menu_core_unregister_node(MenuCoreCtx* ctx, MenuNodeId id); // Main loop void menu_core_loop(MenuCoreCtx* ctx, uint32_t tick); diff --git a/src/core/menu_hash.c b/src/core/menu_hash.c index a8878b4..26d31bb 100644 --- a/src/core/menu_hash.c +++ b/src/core/menu_hash.c @@ -8,64 +8,26 @@ MenuErrCode menu_hash_init(MenuCoreCtx* ctx) { return MENU_ERR_OK; } -MenuErrCode menu_hash_insert(MenuCoreCtx* ctx, MenuNodeId id) { +MenuErrCode menu_hash_insert(MenuCoreCtx* ctx, MenuNodeId id, uint16_t index) { if (!ctx) return MENU_ERR_INVALID_PARAM; - uint16_t index = id % MENU_CONFIG_HASH_TABLE_SIZE; - MenuNodeId current = ctx->hash_table[index]; + uint16_t hash_index = id % MENU_CONFIG_HASH_TABLE_SIZE; + uint16_t current = ctx->hash_table[hash_index]; if (current == 0) { - ctx->hash_table[index] = id; + ctx->hash_table[hash_index] = index + 1; } 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 - + MenuNode* prev_node = &ctx->nodes[current - 1]; 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 + bool found = false; + for (int i = 0; i < MENU_CONFIG_MAX_NODES; i++) { + if (ctx->nodes[i].flags.is_registered && ctx->nodes[i].id == prev_node->hash_next_id) { + prev_node = &ctx->nodes[i]; + found = true; + break; + } + } + if (!found) return MENU_ERR_FAIL; } prev_node->hash_next_id = id; @@ -74,26 +36,34 @@ MenuErrCode menu_hash_insert(MenuCoreCtx* ctx, MenuNodeId id) { return MENU_ERR_OK; } -MenuNodeId menu_hash_find(MenuCoreCtx* ctx, MenuNodeId id) { - if (!ctx) return 0; +MenuNode* menu_hash_find(MenuCoreCtx* ctx, MenuNodeId id) { + if (!ctx) return NULL; - uint16_t index = id % MENU_CONFIG_HASH_TABLE_SIZE; - MenuNodeId current = ctx->hash_table[index]; + uint16_t hash_index = id % MENU_CONFIG_HASH_TABLE_SIZE; + uint16_t current = ctx->hash_table[hash_index]; while (current != 0) { - if (current == id) return current; + uint16_t node_index = current - 1; + MenuNode* node = &ctx->nodes[node_index]; + if (node->flags.is_registered && node->id == id) { + return node; + } - // 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; + for (int i = 0; i < MENU_CONFIG_MAX_NODES; i++) { + if (ctx->nodes[i].flags.is_registered && ctx->nodes[i].id == node->hash_next_id) { + for (int j = 0; j < MENU_CONFIG_MAX_NODES; j++) { + if (ctx->nodes[j].flags.is_registered && ctx->nodes[j].id == node->hash_next_id) { + current = j + 1; + found = true; + break; + } + } break; } } - if (!found) break; + if (!found) break; } - return 0; // Not found -} + return NULL; +} \ No newline at end of file diff --git a/src/core/menu_hash.h b/src/core/menu_hash.h index b0a1e8c..83be97b 100644 --- a/src/core/menu_hash.h +++ b/src/core/menu_hash.h @@ -8,11 +8,11 @@ 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); +MenuErrCode menu_hash_insert(MenuCoreCtx* ctx, MenuNodeId id, uint16_t index); +MenuNode* menu_hash_find(MenuCoreCtx* ctx, MenuNodeId id); #ifdef __cplusplus } #endif -#endif // MENU_HASH_H +#endif // MENU_HASH_H \ No newline at end of file diff --git a/src/core/menu_types.h b/src/core/menu_types.h index a08b4c6..35c225a 100644 --- a/src/core/menu_types.h +++ b/src/core/menu_types.h @@ -17,6 +17,13 @@ struct MenuCoreCtx; typedef uint16_t MenuNodeId; typedef uint8_t MenuPermissionLevel; +// Role structure +typedef struct { + uint8_t id; + const char* name; + MenuPermissionLevel level; +} MenuRole; + // Error codes typedef enum { MENU_ERR_OK = 0, @@ -110,10 +117,19 @@ typedef struct { uint8_t depth; } MenuNavPath; +typedef struct MenuPersistenceData { + MenuNodeId current_node_id; + MenuState current_state; + MenuNavPath nav_path; + MenuStack stack; + uint32_t timestamp; + uint8_t checksum; +} MenuPersistenceData; + // Core Context typedef struct MenuCoreCtx { MenuNode nodes[MENU_CONFIG_MAX_NODES]; - MenuNodeId hash_table[MENU_CONFIG_HASH_TABLE_SIZE]; + uint16_t hash_table[MENU_CONFIG_HASH_TABLE_SIZE]; MenuStack stack; MenuNavPath nav_path; MenuEventQueue event_queue; @@ -132,6 +148,8 @@ typedef struct MenuCoreCtx { // Permission module data struct { uint8_t current_role_id; + MenuRole roles[8]; + uint8_t role_count; } permission; // Persistence module data @@ -143,7 +161,7 @@ typedef struct MenuCoreCtx { struct { uint8_t current_lang_id; } lang; - + } MenuCoreCtx; #ifdef __cplusplus