跳转至

lib/graphic/widget/menu/icontext_menu.c

Icon-text menu widget implementation. More...

Functions

Name
int16_t _calculate_item_x(CFBD_IconTextMenu * pMenu, size_t index)
Calculate the X position for a menu item based on its index and scroll offset.
uint16_t _calculate_item_y(CFBD_IconTextMenu * pMenu)
Calculate the Y position for a menu item (center vertically in viewport)
void _update_icon_widget(CFBD_IconTextMenu * pMenu, CFBD_IconTextMenuItem * item, int16_t item_x)
uint16_t _calculate_text_width(const char * text, uint8_t font_size)
Calculate text pixel width (需要根据实际字体系统实现)
void _update_text_widget(CFBD_IconTextMenu * pMenu, CFBD_IconTextMenuItem * item, int16_t item_x)
CFBD_Bool _is_item_visible(CFBD_IconTextMenu * pMenu, CFBD_IconTextMenuItem * item, int16_t item_x)
Check if item's ICON is within visible viewport.
void _draw_selection_frame(CFBD_IconTextMenu * pMenu, CFBD_IconTextMenuItem * item, int16_t item_x)
Draw selection frame around the selected item's ICON only (not text)
void _center_selected_item(CFBD_IconTextMenu * pMenu)
Update scroll offset to center the selected item.
CFBD_Bool CFBD_IconTextMenuAddItem(CFBD_IconTextMenu * pMenu, const char * label, uint8_t * icon_bitmap, CFBDGraphicSize * icon_size, CFBD_MenuItemCallbackPack * callback)
void CFBD_IconTextMenuScroll(CFBD_IconTextMenu * pMenu, CFBD_IconTextScrollDirection direction)
CFBD_Bool CFBD_IconTextMenuSelectIndex(CFBD_IconTextMenu * pMenu, int index)
void CFBD_IconTextMenuActivateCurrent(CFBD_IconTextMenu * pMenu)
void CFBD_IconTextMenuDraw(CFBD_IconTextMenu * pMenu)
CFBD_Bool CFBD_IconTextMenuUpdate(CFBD_IconTextMenu * pMenu, uint32_t delta_ms)
void CFBD_IconTextMenuSetDimensions(CFBD_IconTextMenu * pMenu, SizeBaseType width, SizeBaseType height, uint16_t spacing)
CFBD_IconTextMenuItem * CFBD_IconTextMenuGetSelectedItem(CFBD_IconTextMenu * pMenu)
CFBD_IconTextMenuItem * CFBD_IconTextMenuGetItem(CFBD_IconTextMenu * pMenu, int index)
void CFBD_IconTextMenuSetCircular(CFBD_IconTextMenu * pMenu, CFBD_Bool is_circular)
void CFBD_IconTextMenuSetSelectionBorder(CFBD_IconTextMenu * pMenu, uint16_t border_width)
void CFBD_IconTextMenuAnimateScroll(CFBD_IconTextMenu * pMenu, CFBD_IconTextScrollDirection direction)

Attributes

Name
CFBD_IconTextMenuOps _cfbd_icontext_menu_ops

Detailed Description

Icon-text menu widget implementation.

Implementation of horizontal scrollable menu with icon+text items, smooth animations, and touch-based navigation.

Functions Documentation

function _calculate_item_x

static int16_t _calculate_item_x(
    CFBD_IconTextMenu * pMenu,
    size_t index
)

Calculate the X position for a menu item based on its index and scroll offset.

Parameters:

  • pMenu - Menu structure
  • index - Item index

Return: int16_t - Calculated X position (可以为负数)

function _calculate_item_y

static uint16_t _calculate_item_y(
    CFBD_IconTextMenu * pMenu
)

Calculate the Y position for a menu item (center vertically in viewport)

Parameters:

  • pMenu - Menu structure

Return: uint16_t - Calculated Y position

function _update_icon_widget

static void _update_icon_widget(
    CFBD_IconTextMenu * pMenu,
    CFBD_IconTextMenuItem * item,
    int16_t item_x
)

function _calculate_text_width

static uint16_t _calculate_text_width(
    const char * text,
    uint8_t font_size
)

Calculate text pixel width (需要根据实际字体系统实现)

Parameters:

  • text - Text string
  • font_size - Font size

Return: uint16_t - Text width in pixels

function _update_text_widget

static void _update_text_widget(
    CFBD_IconTextMenu * pMenu,
    CFBD_IconTextMenuItem * item,
    int16_t item_x
)

function _is_item_visible

static CFBD_Bool _is_item_visible(
    CFBD_IconTextMenu * pMenu,
    CFBD_IconTextMenuItem * item,
    int16_t item_x
)

Check if item's ICON is within visible viewport.

Parameters:

  • pMenu - Menu structure
  • item - Menu item
  • item_x - Item's calculated X position

Return: CFBD_Bool - CFBD_TRUE if icon is visible

function _draw_selection_frame

static void _draw_selection_frame(
    CFBD_IconTextMenu * pMenu,
    CFBD_IconTextMenuItem * item,
    int16_t item_x
)

Draw selection frame around the selected item's ICON only (not text)

Parameters:

  • pMenu - Menu structure
  • item - Current item
  • item_x - Item's X position

function _center_selected_item

static void _center_selected_item(
    CFBD_IconTextMenu * pMenu
)

Update scroll offset to center the selected item.

Parameters:

  • pMenu - Menu structure

function CFBD_IconTextMenuAddItem

static CFBD_Bool CFBD_IconTextMenuAddItem(
    CFBD_IconTextMenu * pMenu,
    const char * label,
    uint8_t * icon_bitmap,
    CFBDGraphicSize * icon_size,
    CFBD_MenuItemCallbackPack * callback
)

function CFBD_IconTextMenuScroll

static void CFBD_IconTextMenuScroll(
    CFBD_IconTextMenu * pMenu,
    CFBD_IconTextScrollDirection direction
)

function CFBD_IconTextMenuSelectIndex

static CFBD_Bool CFBD_IconTextMenuSelectIndex(
    CFBD_IconTextMenu * pMenu,
    int index
)

function CFBD_IconTextMenuActivateCurrent

static void CFBD_IconTextMenuActivateCurrent(
    CFBD_IconTextMenu * pMenu
)

function CFBD_IconTextMenuDraw

static void CFBD_IconTextMenuDraw(
    CFBD_IconTextMenu * pMenu
)

function CFBD_IconTextMenuUpdate

static CFBD_Bool CFBD_IconTextMenuUpdate(
    CFBD_IconTextMenu * pMenu,
    uint32_t delta_ms
)

function CFBD_IconTextMenuSetDimensions

static void CFBD_IconTextMenuSetDimensions(
    CFBD_IconTextMenu * pMenu,
    SizeBaseType width,
    SizeBaseType height,
    uint16_t spacing
)

function CFBD_IconTextMenuGetSelectedItem

static CFBD_IconTextMenuItem * CFBD_IconTextMenuGetSelectedItem(
    CFBD_IconTextMenu * pMenu
)

function CFBD_IconTextMenuGetItem

static CFBD_IconTextMenuItem * CFBD_IconTextMenuGetItem(
    CFBD_IconTextMenu * pMenu,
    int index
)

function CFBD_IconTextMenuSetCircular

static void CFBD_IconTextMenuSetCircular(
    CFBD_IconTextMenu * pMenu,
    CFBD_Bool is_circular
)

function CFBD_IconTextMenuSetSelectionBorder

static void CFBD_IconTextMenuSetSelectionBorder(
    CFBD_IconTextMenu * pMenu,
    uint16_t border_width
)

function CFBD_IconTextMenuAnimateScroll

static void CFBD_IconTextMenuAnimateScroll(
    CFBD_IconTextMenu * pMenu,
    CFBD_IconTextScrollDirection direction
)

Attributes Documentation

variable _cfbd_icontext_menu_ops

static CFBD_IconTextMenuOps _cfbd_icontext_menu_ops = {
        .add_item = CFBD_IconTextMenuAddItem,
        .scroll = CFBD_IconTextMenuScroll,
        .select_index = CFBD_IconTextMenuSelectIndex,
        .activate_current = CFBD_IconTextMenuActivateCurrent,
        .immediate_draw = CFBD_IconTextMenuDraw,
        .update = CFBD_IconTextMenuUpdate,
        .set_dimensions = CFBD_IconTextMenuSetDimensions,
        .get_selected_item = CFBD_IconTextMenuGetSelectedItem,
        .get_item = CFBD_IconTextMenuGetItem,
        .set_circular = CFBD_IconTextMenuSetCircular,
        .set_selection_border = CFBD_IconTextMenuSetSelectionBorder,
        .animate_scroll = CFBD_IconTextMenuAnimateScroll};

Source code

#include "icontext_menu.h"

#include <stdint.h>
#include <string.h>

#include "base/rectangle.h"
#include "cfbd_define.h"
#include "device/graphic_device.h"
#include "sys_clock/system_clock.h"
#include "widget/base_support/common/helpers.h"
#include "widget/text_config.h"

static int16_t _calculate_item_x(CFBD_IconTextMenu* pMenu, size_t index)
{
    int32_t x =
            pMenu->viewport.tl.x + (int32_t) (index * (pMenu->item_width + pMenu->item_spacing));
    x += pMenu->scroll_offset;
    return (int16_t) x;
}

static uint16_t _calculate_item_y(CFBD_IconTextMenu* pMenu)
{
    uint16_t viewport_height = pMenu->viewport.br.y - pMenu->viewport.tl.y;
    int16_t y_offset = (viewport_height - pMenu->item_height) / 2;
    return pMenu->viewport.tl.y + y_offset;
}

static void
_update_icon_widget(CFBD_IconTextMenu* pMenu, CFBD_IconTextMenuItem* item, int16_t item_x)
{
    int16_t icon_x = item_x + (int16_t) ((pMenu->item_width - item->icon_size.width) / 2);
    uint16_t icon_y = _calculate_item_y(pMenu) + CFBD_ICONTEXT_MENU_ICON_TEXT_GAP;

    item->icon_widget.point.x = (uint16_t) icon_x;
    item->icon_widget.point.y = icon_y;
}

static uint16_t _calculate_text_width(const char* text, uint8_t font_size)
{
    // 简单估算:每个字符宽度 = font_size * 0.6 (需要根据实际字体调整)
    // 或者调用实际的字体宽度计算函数
    if (!text)
        return 0;

    uint16_t char_count = strlen(text);
    uint16_t fwidth = __fetch_font_size(font_size).width;
    return char_count * fwidth;
}

static void
_update_text_widget(CFBD_IconTextMenu* pMenu, CFBD_IconTextMenuItem* item, int16_t item_x)
{
    uint16_t text_y = _calculate_item_y(pMenu) + item->icon_size.height +
                      CFBD_ICONTEXT_MENU_ICON_TEXT_GAP * 2;

    // 计算文字实际宽度
    uint16_t text_width = _calculate_text_width(item->label, CFBD_ICONTEXT_MENU_TEXT_SIZE);

    uint16_t text_x;
    if (text_width < pMenu->item_width) {
        // 文字宽度小于item宽度:居中对齐
        text_x = item_x + (pMenu->item_width - text_width) / 2;
    }
    else {
        // 文字宽度大于等于item宽度:左对齐
        text_x = item_x;
    }

    item->text_widget.tl_point.x = text_x;
    item->text_widget.tl_point.y = text_y;
    item->text_widget.indexed_point = item->text_widget.tl_point;

    item->text_widget.TexthandleSize.width = pMenu->item_width;
    item->text_widget.TexthandleSize.height = 8;
}

static CFBD_Bool
_is_item_visible(CFBD_IconTextMenu* pMenu, CFBD_IconTextMenuItem* item, int16_t item_x)
{
    // 计算图标的实际位置(居中在item中)
    int16_t icon_x = item_x + (int16_t) ((pMenu->item_width - item->icon_size.width) / 2);
    int16_t icon_right = icon_x + (int16_t) item->icon_size.width;

    int16_t viewport_left = pMenu->viewport.tl.x;
    int16_t viewport_right = pMenu->viewport.br.x;

    // 检查图标(而不是item)是否与视口有交集
    return (icon_right > viewport_left) && (icon_x < viewport_right);
}

static void
_draw_selection_frame(CFBD_IconTextMenu* pMenu, CFBD_IconTextMenuItem* item, int16_t item_x)
{
    if (!pMenu || !pMenu->device || !item) {
        return;
    }

    // Calculate icon position (same as _update_icon_widget)
    int16_t icon_x = item_x + (int16_t) ((pMenu->item_width - item->icon_size.width) / 2);
    uint16_t icon_y = _calculate_item_y(pMenu) + CFBD_ICONTEXT_MENU_ICON_TEXT_GAP;

    // Frame around icon only (not text)
    int16_t frame_x = icon_x - pMenu->selection_border_width;
    int16_t frame_y = icon_y - pMenu->selection_border_width;
    uint16_t frame_width = item->icon_size.width + 2 * pMenu->selection_border_width;
    uint16_t frame_height = item->icon_size.height + 2 * pMenu->selection_border_width;

    uint16_t border = pMenu->selection_border_width;

    // Draw four borders using draw_pixel
    // Top border
    for (uint16_t y = 0; y < border; y++) {
        for (uint16_t x = 0; x < frame_width; x++) {
            pMenu->device->ops->setPixel(pMenu->device, frame_x + x, frame_y + y);
        }
    }

    // Bottom border
    for (uint16_t y = 0; y < border; y++) {
        for (uint16_t x = 0; x < frame_width; x++) {
            pMenu->device->ops->setPixel(pMenu->device,
                                         frame_x + x,
                                         frame_y + frame_height - border + y);
        }
    }

    // Left border
    for (uint16_t y = border; y < frame_height - border; y++) {
        for (uint16_t x = 0; x < border; x++) {
            pMenu->device->ops->setPixel(pMenu->device, frame_x + x, frame_y + y);
        }
    }

    // Right border
    for (uint16_t y = border; y < frame_height - border; y++) {
        for (uint16_t x = 0; x < border; x++) {
            pMenu->device->ops->setPixel(pMenu->device,
                                         frame_x + frame_width - border + x,
                                         frame_y + y);
        }
    }
}

static void _center_selected_item(CFBD_IconTextMenu* pMenu)
{
    if (!pMenu) {
        return;
    }

    int16_t viewport_width = pMenu->viewport.br.x - pMenu->viewport.tl.x;
    int16_t center_offset = (viewport_width - pMenu->item_width) / 2;

    pMenu->scroll_offset =
            -pMenu->selected_index * (pMenu->item_width + pMenu->item_spacing) + center_offset;
    pMenu->target_scroll_offset = pMenu->scroll_offset;
    pMenu->prev_scroll_offset = pMenu->scroll_offset;
}

/* ========== Static Implementation Functions (prefixed with CFBD_) ========== */

static CFBD_Bool CFBD_IconTextMenuAddItem(CFBD_IconTextMenu* pMenu,
                                          const char* label,
                                          uint8_t* icon_bitmap,
                                          CFBDGraphicSize* icon_size,
                                          CFBD_MenuItemCallbackPack* callback)
{
    if (!pMenu || pMenu->item_count >= pMenu->capacity || !label || !icon_bitmap) {
        return CFBD_FALSE;
    }

    CFBD_IconTextMenuItem* item = &pMenu->items[pMenu->item_count];

    item->label = label;
    item->icon_bitmap = icon_bitmap;
    item->icon_size = *icon_size;
    item->state = CFBD_ICONTEXT_ITEM_NORMAL;
    item->user_data = NULL;

    if (callback) {
        item->callback = *callback;
    }
    else {
        item->callback.callback = NULL;
        item->callback.user_data = NULL;
    }

    item->render_position.x = 0;
    item->render_position.y = 0;

    CFBDGraphic_InitImage(&item->icon_widget, &item->render_position, icon_size, icon_bitmap);

    CFBDGraphicSize text_area = {.width = pMenu->item_width, .height = 8};
    CFBDGraphic_InitText(&item->text_widget,
                         item->render_position,
                         text_area,
                         CFBD_ICONTEXT_MENU_TEXT_SIZE);
    item->text_widget.no_wrap = CFBD_TRUE;
    CFBDGraphic_SetText(&item->text_widget, (char*) label);

    pMenu->item_count++;
    return CFBD_TRUE;
}

static void CFBD_IconTextMenuScroll(CFBD_IconTextMenu* pMenu,
                                    CFBD_IconTextScrollDirection direction)
{
    if (!pMenu || pMenu->item_count == 0 || pMenu->is_animating) {
        return;
    }

    int new_index = pMenu->selected_index;

    if (direction == CFBD_ICONTEXT_SCROLL_RIGHT) {
        new_index++;
        if (new_index >= (int) pMenu->item_count) {
            if (pMenu->is_circular) {
                new_index = 0;
            }
            else {
                return;
            }
        }
    }
    else if (direction == CFBD_ICONTEXT_SCROLL_LEFT) {
        new_index--;
        if (new_index < 0) {
            if (pMenu->is_circular) {
                new_index = pMenu->item_count - 1;
            }
            else {
                return;
            }
        }
    }
    else {
        return;
    }

    pMenu->selected_index = new_index;
    pMenu->scroll_direction = direction;

    // Calculate target offset to center the new selected item
    int16_t viewport_width = pMenu->viewport.br.x - pMenu->viewport.tl.x;
    int16_t center_offset = (viewport_width - pMenu->item_width) / 2;
    pMenu->target_scroll_offset =
            -new_index * (pMenu->item_width + pMenu->item_spacing) + center_offset;

    pMenu->animation_start_offset = pMenu->scroll_offset;
    pMenu->animation_elapsed_ms = 0;
    pMenu->anim_current_frame = 0;
    pMenu->is_animating = CFBD_TRUE;
}

static CFBD_Bool CFBD_IconTextMenuSelectIndex(CFBD_IconTextMenu* pMenu, int index)
{
    if (!pMenu || index < 0 || index >= (int) pMenu->item_count) {
        return CFBD_FALSE;
    }

    pMenu->selected_index = index;

    // Center the selected item immediately
    _center_selected_item(pMenu);

    return CFBD_TRUE;
}

static void CFBD_IconTextMenuActivateCurrent(CFBD_IconTextMenu* pMenu)
{
    if (!pMenu || pMenu->selected_index < 0 || pMenu->selected_index >= (int) pMenu->item_count) {
        return;
    }

    CFBD_IconTextMenuItem* item = &pMenu->items[pMenu->selected_index];

    item->state = CFBD_ICONTEXT_ITEM_PRESSED;

    if (item->callback.callback) {
        item->callback.callback(item->callback.user_data);
    }

    item->state = CFBD_ICONTEXT_ITEM_FOCUSED;
}

static void CFBD_IconTextMenuDraw(CFBD_IconTextMenu* pMenu)
{
    if (!pMenu || !pMenu->device || pMenu->item_count == 0) {
        return;
    }

    // 【修复1】扩大清除区域,确保边框也被清除
    int16_t clear_x = pMenu->viewport.tl.x;
    int16_t clear_y = pMenu->viewport.tl.y;
    uint16_t clear_w = rect_width(&pMenu->viewport);
    uint16_t clear_h = rect_height(&pMenu->viewport);

    // 向外扩展边框宽度的距离
    if (clear_x >= pMenu->selection_border_width) {
        clear_x -= pMenu->selection_border_width;
        clear_w += pMenu->selection_border_width * 2;
    }
    if (clear_y >= pMenu->selection_border_width) {
        clear_y -= pMenu->selection_border_width;
        clear_h += pMenu->selection_border_width * 2;
    }

    pMenu->device->ops->clear_area(pMenu->device, clear_x, clear_y, clear_w, clear_h);

    for (size_t i = 0; i < pMenu->item_count; i++) {
        CFBD_IconTextMenuItem* item = &pMenu->items[i];
        int16_t item_x = _calculate_item_x(pMenu, i);

        if (!_is_item_visible(pMenu, item, item_x)) {
            continue;
        }

        _update_icon_widget(pMenu, item, item_x);
        _update_text_widget(pMenu, item, item_x);

        if ((int) i == pMenu->selected_index) {
            _draw_selection_frame(pMenu, item, item_x);
        }

        if (item->icon_bitmap) {
            CFBDGraphic_DrawImageClipped(pMenu->device, &item->icon_widget, &pMenu->viewport);
        }

        CFBDGraphic_DrawText(pMenu->device,
                             &item->text_widget,
                             CCGraphic_AsciiTextItem_RequestOldPoint);
    }

    pMenu->device->ops->update_area(pMenu->device,
                                    pMenu->viewport.tl.x,
                                    pMenu->viewport.tl.y,
                                    rect_width(&pMenu->viewport),
                                    rect_height(&pMenu->viewport));

    pMenu->prev_scroll_offset = pMenu->scroll_offset;
}

static CFBD_Bool CFBD_IconTextMenuUpdate(CFBD_IconTextMenu* pMenu, uint32_t delta_ms)
{
    (void) delta_ms;

    if (!pMenu || !pMenu->is_animating)
        return CFBD_FALSE;

    uint8_t max_frames = pMenu->scroll_animation.anim_frames;

    if (pMenu->anim_current_frame < max_frames)
        pMenu->anim_current_frame++;

    int32_t start = pMenu->animation_start_offset;
    int32_t delta = (int32_t) pMenu->target_scroll_offset - start;

    pMenu->scroll_offset = (int16_t) (start + (delta * pMenu->anim_current_frame) / max_frames);

    if (pMenu->anim_current_frame >= max_frames) {
        pMenu->scroll_offset = pMenu->target_scroll_offset;
        pMenu->is_animating = CFBD_FALSE;
        pMenu->anim_current_frame = 0;
        return CFBD_FALSE;
    }

    return CFBD_TRUE;
}

static void CFBD_IconTextMenuSetDimensions(CFBD_IconTextMenu* pMenu,
                                           SizeBaseType width,
                                           SizeBaseType height,
                                           uint16_t spacing)
{
    pMenu->item_width = width;
    pMenu->item_height = height;
    pMenu->item_spacing = spacing;
}

static CFBD_IconTextMenuItem* CFBD_IconTextMenuGetSelectedItem(CFBD_IconTextMenu* pMenu)
{
    if (!pMenu || pMenu->selected_index < 0 || pMenu->selected_index >= (int) pMenu->item_count) {
        return NULL;
    }
    return &pMenu->items[pMenu->selected_index];
}

static CFBD_IconTextMenuItem* CFBD_IconTextMenuGetItem(CFBD_IconTextMenu* pMenu, int index)
{
    if (!pMenu || index < 0 || index >= (int) pMenu->item_count) {
        return NULL;
    }
    return &pMenu->items[index];
}

static void CFBD_IconTextMenuSetCircular(CFBD_IconTextMenu* pMenu, CFBD_Bool is_circular)
{
    if (!pMenu) {
        return;
    }
    pMenu->is_circular = is_circular;
}

static void CFBD_IconTextMenuSetSelectionBorder(CFBD_IconTextMenu* pMenu, uint16_t border_width)
{
    if (!pMenu) {
        return;
    }
    pMenu->selection_border_width = border_width;
}

static void CFBD_IconTextMenuAnimateScroll(CFBD_IconTextMenu* pMenu,
                                           CFBD_IconTextScrollDirection direction)
{
    if (!pMenu || pMenu->item_count == 0) {
        return;
    }

    pMenu->operations->scroll(pMenu, direction);

    if (!pMenu->is_animating) {
        return;
    }

    uint32_t frame_delay = pMenu->scroll_animation.anim_frame_delay_ms;

    while (pMenu->is_animating) {
        pMenu->operations->update(pMenu, 0);
        pMenu->operations->immediate_draw(pMenu);

        if (frame_delay > 0) {
            system_delay_ms(frame_delay);
        }
    }
}

/* ========== Operation Table ========== */

static CFBD_IconTextMenuOps _cfbd_icontext_menu_ops = {
        .add_item = CFBD_IconTextMenuAddItem,
        .scroll = CFBD_IconTextMenuScroll,
        .select_index = CFBD_IconTextMenuSelectIndex,
        .activate_current = CFBD_IconTextMenuActivateCurrent,
        .immediate_draw = CFBD_IconTextMenuDraw,
        .update = CFBD_IconTextMenuUpdate,
        .set_dimensions = CFBD_IconTextMenuSetDimensions,
        .get_selected_item = CFBD_IconTextMenuGetSelectedItem,
        .get_item = CFBD_IconTextMenuGetItem,
        .set_circular = CFBD_IconTextMenuSetCircular,
        .set_selection_border = CFBD_IconTextMenuSetSelectionBorder,
        .animate_scroll = CFBD_IconTextMenuAnimateScroll};

/* ========== Public API ========== */

void CFBD_InitIconTextMenu(CFBD_IconTextMenu* pMenu,
                           CFBD_GraphicDevice* device,
                           CFBDGraphicRect* viewport,
                           CFBD_IconTextMenuItem* items_buffer,
                           size_t capacity)
{
    pMenu->operations = &_cfbd_icontext_menu_ops;
    pMenu->items = items_buffer;
    memset(pMenu->items, 0, capacity * sizeof(CFBD_IconTextMenuItem));

    pMenu->device = device;
    pMenu->viewport = *viewport;
    pMenu->capacity = capacity;
    pMenu->item_count = 0;

    pMenu->selected_index = 0;
    pMenu->first_visible_index = 0;
    pMenu->scroll_offset = 0;
    pMenu->prev_scroll_offset = 0;
    pMenu->target_scroll_offset = 0;
    pMenu->scroll_direction = CFBD_ICONTEXT_SCROLL_NONE;
    pMenu->item_width = CFBD_ICONTEXT_MENU_DEFAULT_ITEM_WIDTH;
    pMenu->item_height = CFBD_ICONTEXT_MENU_DEFAULT_ITEM_HEIGHT;
    pMenu->item_spacing = 5;
    pMenu->is_animating = CFBD_FALSE;
    pMenu->is_circular = CFBD_FALSE;

    pMenu->selection_border_width = 1;

    pMenu->animation_start_offset = 0;
    pMenu->animation_elapsed_ms = 0;
    pMenu->anim_current_frame = 0;

    CFBD_InitBaseAnimation(&pMenu->scroll_animation);
    pMenu->scroll_animation.anim_frames = 10;
    pMenu->scroll_animation.anim_frame_delay_ms = 30;
}

Updated on 2026-02-03 at 13:21:55 +0000