Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 19 additions & 31 deletions src/input.c
Original file line number Diff line number Diff line change
Expand Up @@ -1438,19 +1438,7 @@ bool iui_radio(iui_context *ctx,
* TextField-style component that reveals a menu of options
*/

/* Static state for dropdown menu (one active at a time) */
/* Dropdown menu state - single instance per UI context.
* Thread-safety: This static state assumes single-threaded UI rendering.
* Each thread/context should have its own UI instance. If multi-context
* support is needed, move this state into iui_context.
*/
static struct {
bool open; /* menu is visible */
float x, y, width; /* menu position and width */
int hovered_index; /* currently hovered option */
int frames_since_open; /* frame counter for click protection */
const int *selected; /* pointer to track which dropdown is active */
} s_dropdown = {0};


bool iui_dropdown(iui_context *ctx, const iui_dropdown_options *options)
{
Expand Down Expand Up @@ -1481,8 +1469,8 @@ bool iui_dropdown(iui_context *ctx, const iui_dropdown_options *options)
};

/* Check if this dropdown's menu is open */
bool is_open =
s_dropdown.open && (s_dropdown.selected == options->selected_index);
bool is_open = ctx->dropdown.open &&
(ctx->dropdown.selected == options->selected_index);

/* Get field interaction state */
iui_state_t state =
Expand Down Expand Up @@ -1560,36 +1548,36 @@ bool iui_dropdown(iui_context *ctx, const iui_dropdown_options *options)
if (!options->disabled && state == IUI_STATE_PRESSED) {
if (is_open) {
/* Close menu */
s_dropdown.open = false;
ctx->dropdown.open = false;
} else {
/* Open menu */
s_dropdown.open = true;
s_dropdown.x = field_rect.x;
s_dropdown.y = field_rect.y + field_h;
s_dropdown.width = field_rect.width;
s_dropdown.hovered_index = selected;
s_dropdown.frames_since_open = 0;
s_dropdown.selected = options->selected_index;
ctx->dropdown.open = true;
ctx->dropdown.x = field_rect.x;
ctx->dropdown.y = field_rect.y + field_h;
ctx->dropdown.width = field_rect.width;
ctx->dropdown.hovered_index = selected;
ctx->dropdown.frames_since_open = 0;
ctx->dropdown.selected = options->selected_index;
}
}

/* Draw menu if open */
if (is_open) {
/* Capture frame count BEFORE incrementing for click protection.
* This ensures the first frame after opening (frames_active == 0)
* This ensures that first frame after opening (frames_active == 0)
* blocks clicks, preventing click-through from the field that opened
* this menu.
* menu.
*/
int frames_active = s_dropdown.frames_since_open++;
int frames_active = ctx->dropdown.frames_since_open++;

/* Calculate menu height (capped at max) */
float item_h = IUI_DROPDOWN_ITEM_HEIGHT;
float menu_h = options->option_count * item_h;
if (menu_h > IUI_DROPDOWN_MENU_MAX_HEIGHT)
menu_h = IUI_DROPDOWN_MENU_MAX_HEIGHT;

iui_rect_t menu_rect = {s_dropdown.x, s_dropdown.y, s_dropdown.width,
menu_h};
iui_rect_t menu_rect = {ctx->dropdown.x, ctx->dropdown.y,
ctx->dropdown.width, menu_h};

/* Begin modal for menu */
iui_begin_modal(ctx, "dropdown_menu_modal");
Expand All @@ -1612,7 +1600,7 @@ bool iui_dropdown(iui_context *ctx, const iui_dropdown_options *options)
/* Update hovered index */
if (item_state == IUI_STATE_HOVERED ||
item_state == IUI_STATE_PRESSED) {
s_dropdown.hovered_index = i;
ctx->dropdown.hovered_index = i;
}

/* Draw selection/hover background */
Expand Down Expand Up @@ -1640,7 +1628,7 @@ bool iui_dropdown(iui_context *ctx, const iui_dropdown_options *options)
if (frames_active >= 1 && item_state == IUI_STATE_PRESSED) {
*options->selected_index = i;
selection_changed = (i != selected);
s_dropdown.open = false;
ctx->dropdown.open = false;
iui_close_modal(ctx);
}
}
Expand All @@ -1652,7 +1640,7 @@ bool iui_dropdown(iui_context *ctx, const iui_dropdown_options *options)
bool mouse_pressed = (ctx->mouse_pressed & IUI_MOUSE_LEFT);

if (!mouse_in_menu && !mouse_in_field && mouse_pressed) {
s_dropdown.open = false;
ctx->dropdown.open = false;
iui_close_modal(ctx);
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,14 @@ struct iui_context {
int menu_item_index;
float menu_prev_height;

struct {
bool open;
float x, y, width;
int hovered_index;
int frames_since_open;
const int *selected;
} dropdown;

/* COOL PATH - Scroll State */
iui_scroll_state *active_scroll;
iui_scroll_state *scroll_dragging;
Expand Down
Loading