@@ -1130,6 +1130,132 @@ iui_textfield_result iui_textfield_with_selection(
11301130 return result ;
11311131}
11321132
1133+ /* Toggle Widget Helper - shared logic for checkbox and radio */
1134+
1135+ typedef void (* iui_toggle_draw_fn )(iui_context * ctx ,
1136+ iui_rect_t widget_rect ,
1137+ float corner ,
1138+ bool is_focused ,
1139+ bool is_active ,
1140+ void * user_data );
1141+
1142+ static void checkbox_draw (iui_context * ctx ,
1143+ iui_rect_t widget_rect ,
1144+ float corner ,
1145+ bool is_focused ,
1146+ bool is_active ,
1147+ void * user_data )
1148+ {
1149+ (void ) user_data ;
1150+ if (is_active ) {
1151+ uint32_t bg_color = ctx -> colors .primary ;
1152+ if (is_focused ) {
1153+ uint32_t focus_layer =
1154+ iui_state_layer (ctx -> colors .on_primary , IUI_STATE_FOCUS_ALPHA );
1155+ bg_color = iui_blend_color (bg_color , focus_layer );
1156+ }
1157+ ctx -> renderer .draw_box (widget_rect , corner , bg_color ,
1158+ ctx -> renderer .user );
1159+ float mark_margin = widget_rect .width * 0.25f ;
1160+ ctx -> renderer .draw_box (
1161+ (iui_rect_t ) {widget_rect .x + mark_margin ,
1162+ widget_rect .y + mark_margin ,
1163+ widget_rect .width - mark_margin * 2 ,
1164+ widget_rect .height - mark_margin * 2 },
1165+ corner * 0.5f , ctx -> colors .on_primary , ctx -> renderer .user );
1166+ } else {
1167+ uint32_t bg_color = ctx -> colors .surface_container ;
1168+ if (is_focused ) {
1169+ uint32_t focus_layer =
1170+ iui_state_layer (ctx -> colors .primary , IUI_STATE_FOCUS_ALPHA );
1171+ bg_color = iui_blend_color (bg_color , focus_layer );
1172+ }
1173+ ctx -> renderer .draw_box (widget_rect , corner , bg_color ,
1174+ ctx -> renderer .user );
1175+ }
1176+ }
1177+
1178+ static void radio_draw (iui_context * ctx ,
1179+ iui_rect_t widget_rect ,
1180+ float corner ,
1181+ bool is_focused ,
1182+ bool is_active ,
1183+ void * user_data )
1184+ {
1185+ (void ) user_data ;
1186+ uint32_t bg_color =
1187+ is_active ? ctx -> colors .primary : ctx -> colors .surface_container ;
1188+ if (is_focused ) {
1189+ uint32_t focus_layer = iui_state_layer (
1190+ is_active ? ctx -> colors .on_primary : ctx -> colors .primary ,
1191+ IUI_STATE_FOCUS_ALPHA );
1192+ bg_color = iui_blend_color (bg_color , focus_layer );
1193+ }
1194+ ctx -> renderer .draw_box (widget_rect , corner , bg_color , ctx -> renderer .user );
1195+
1196+ if (is_active ) {
1197+ float dot_size = widget_rect .width * 0.5f ;
1198+ float dot_margin = (widget_rect .width - dot_size ) * 0.5f ;
1199+ ctx -> renderer .draw_box (
1200+ (iui_rect_t ) {widget_rect .x + dot_margin ,
1201+ widget_rect .y + dot_margin , dot_size , dot_size },
1202+ dot_size * 0.5f , ctx -> colors .on_primary , ctx -> renderer .user );
1203+ }
1204+ }
1205+
1206+ static bool toggle_base (iui_context * ctx ,
1207+ const char * label ,
1208+ iui_rect_t widget_rect ,
1209+ iui_rect_t hit_rect ,
1210+ float corner ,
1211+ void * value ,
1212+ int radio_value ,
1213+ bool is_radio ,
1214+ iui_toggle_draw_fn draw_fn ,
1215+ void * draw_user_data )
1216+ {
1217+ uint32_t widget_id = iui_widget_id (label , widget_rect );
1218+ iui_register_focusable (ctx , widget_id , widget_rect , corner );
1219+ bool is_focused = iui_widget_is_focused (ctx , widget_id );
1220+
1221+ iui_state_t state = iui_get_component_state (ctx , hit_rect , false);
1222+ bool should_toggle = (state == IUI_STATE_PRESSED );
1223+ if (is_focused && (ctx -> key_pressed == IUI_KEY_ENTER )) {
1224+ should_toggle = true;
1225+ ctx -> key_pressed = IUI_KEY_NONE ;
1226+ }
1227+
1228+ bool toggled = false;
1229+ bool is_active = false;
1230+
1231+ if (should_toggle ) {
1232+ toggled = true;
1233+ if (is_radio ) {
1234+ * (int * ) value = radio_value ;
1235+ } else {
1236+ * (bool * ) value = !(* (bool * ) value );
1237+ }
1238+ }
1239+
1240+ if (is_radio ) {
1241+ is_active = (* (int * ) value == radio_value );
1242+ } else {
1243+ is_active = * (bool * ) value ;
1244+ }
1245+
1246+ if (is_focused )
1247+ iui_draw_focus_ring (ctx , widget_rect , corner );
1248+
1249+ draw_fn (ctx , widget_rect , corner , is_focused , is_active , draw_user_data );
1250+
1251+ float text_x = widget_rect .x + widget_rect .width + ctx -> padding ;
1252+ float text_y = ctx -> layout .y + (ctx -> row_height - ctx -> font_height ) * 0.5f ;
1253+ iui_internal_draw_text (ctx , text_x , text_y , label , ctx -> colors .on_surface );
1254+
1255+ iui_newline (ctx );
1256+ return toggled ;
1257+ }
1258+
11331259/* Switch Widget */
11341260
11351261bool iui_switch (iui_context * ctx ,
@@ -1286,12 +1412,6 @@ bool iui_checkbox(iui_context *ctx, const char *label, bool *checked)
12861412 if (!ctx -> current_window || !label )
12871413 return false;
12881414
1289- /* Modal blocking is handled centrally by iui_get_component_state() which
1290- * returns IUI_STATE_DEFAULT when modal is active and rendering=false
1291- */
1292-
1293- bool toggled = false;
1294-
12951415 float box_size = ctx -> font_height ;
12961416 float corner = box_size * 0.15f ;
12971417 iui_rect_t box_rect = {
@@ -1308,63 +1428,8 @@ bool iui_checkbox(iui_context *ctx, const char *label, bool *checked)
13081428 .height = ctx -> row_height ,
13091429 };
13101430
1311- /* Register as focusable widget for keyboard navigation.
1312- * Combine label hash with position to avoid ID collision.
1313- */
1314- uint32_t widget_id = iui_widget_id (label , box_rect );
1315- iui_register_focusable (ctx , widget_id , box_rect , corner );
1316- bool is_focused = iui_widget_is_focused (ctx , widget_id );
1317-
1318- /* Toggle on click or keyboard activation */
1319- iui_state_t state = iui_get_component_state (ctx , hit_rect , false);
1320- bool should_toggle = (state == IUI_STATE_PRESSED );
1321- if (is_focused && (ctx -> key_pressed == IUI_KEY_ENTER )) {
1322- should_toggle = true;
1323- ctx -> key_pressed = IUI_KEY_NONE ;
1324- }
1325- if (should_toggle ) {
1326- * checked = !(* checked );
1327- toggled = true;
1328- }
1329-
1330- /* Draw focus ring when focused */
1331- if (is_focused )
1332- iui_draw_focus_ring (ctx , box_rect , corner );
1333-
1334- if (* checked ) {
1335- /* Checked: filled box with primary, inner mark with on_primary */
1336- uint32_t bg_color = ctx -> colors .primary ;
1337- if (is_focused ) {
1338- uint32_t focus_layer =
1339- iui_state_layer (ctx -> colors .on_primary , IUI_STATE_FOCUS_ALPHA );
1340- bg_color = iui_blend_color (bg_color , focus_layer );
1341- }
1342- ctx -> renderer .draw_box (box_rect , corner , bg_color , ctx -> renderer .user );
1343- /* Checkmark (simplified as smaller inner square) */
1344- float mark_margin = box_size * 0.25f ;
1345- ctx -> renderer .draw_box (
1346- (iui_rect_t ) {box_rect .x + mark_margin , box_rect .y + mark_margin ,
1347- box_size - mark_margin * 2 ,
1348- box_size - mark_margin * 2 },
1349- corner * 0.5f , ctx -> colors .on_primary , ctx -> renderer .user );
1350- } else {
1351- /* Unchecked: surface_variant background */
1352- uint32_t bg_color = ctx -> colors .surface_container ;
1353- if (is_focused ) {
1354- uint32_t focus_layer =
1355- iui_state_layer (ctx -> colors .primary , IUI_STATE_FOCUS_ALPHA );
1356- bg_color = iui_blend_color (bg_color , focus_layer );
1357- }
1358- ctx -> renderer .draw_box (box_rect , corner , bg_color , ctx -> renderer .user );
1359- }
1360-
1361- /* Draw label */
1362- float text_x = box_rect .x + box_size + ctx -> padding ;
1363- float text_y = ctx -> layout .y + (ctx -> row_height - ctx -> font_height ) * 0.5f ;
1364- iui_internal_draw_text (ctx , text_x , text_y , label , ctx -> colors .on_surface );
1365-
1366- iui_newline (ctx );
1367- return toggled ;
1431+ return toggle_base (ctx , label , box_rect , hit_rect , corner , checked , 0 ,
1432+ false, checkbox_draw , NULL );
13681433}
13691434
13701435bool iui_radio (iui_context * ctx ,
@@ -1374,7 +1439,6 @@ bool iui_radio(iui_context *ctx,
13741439{
13751440 if (!ctx -> current_window || !label )
13761441 return false;
1377- bool selected = false;
13781442
13791443 float circle_size = ctx -> font_height ;
13801444 float corner = circle_size * 0.5f ; /* Full circle */
@@ -1392,58 +1456,8 @@ bool iui_radio(iui_context *ctx,
13921456 .height = ctx -> row_height ,
13931457 };
13941458
1395- /* Register as focusable widget for keyboard navigation.
1396- * Combine label hash with position to avoid ID collision. */
1397- uint32_t widget_id = iui_widget_id (label , circle_rect );
1398- iui_register_focusable (ctx , widget_id , circle_rect , corner );
1399- bool is_focused = iui_widget_is_focused (ctx , widget_id );
1400-
1401- /* Select on click or keyboard activation */
1402- iui_state_t state = iui_get_component_state (ctx , hit_rect , false);
1403- bool should_select = (state == IUI_STATE_PRESSED );
1404- if (is_focused && (ctx -> key_pressed == IUI_KEY_ENTER )) {
1405- should_select = true;
1406- ctx -> key_pressed = IUI_KEY_NONE ;
1407- }
1408- if (should_select ) {
1409- * group_value = button_value ;
1410- selected = true;
1411- }
1412-
1413- bool is_selected = (* group_value == button_value );
1414-
1415- /* Draw focus ring when focused */
1416- if (is_focused )
1417- iui_draw_focus_ring (ctx , circle_rect , corner );
1418-
1419- /* Outer circle background with focus state layer */
1420- uint32_t bg_color =
1421- is_selected ? ctx -> colors .primary : ctx -> colors .surface_container ;
1422- if (is_focused ) {
1423- uint32_t focus_layer = iui_state_layer (
1424- is_selected ? ctx -> colors .on_primary : ctx -> colors .primary ,
1425- IUI_STATE_FOCUS_ALPHA );
1426- bg_color = iui_blend_color (bg_color , focus_layer );
1427- }
1428- ctx -> renderer .draw_box (circle_rect , corner , bg_color , ctx -> renderer .user );
1429-
1430- /* Draw inner dot if selected */
1431- if (is_selected ) {
1432- float dot_size = circle_size * 0.5f ;
1433- float dot_margin = (circle_size - dot_size ) * 0.5f ;
1434- ctx -> renderer .draw_box (
1435- (iui_rect_t ) {circle_rect .x + dot_margin ,
1436- circle_rect .y + dot_margin , dot_size , dot_size },
1437- dot_size * 0.5f , ctx -> colors .on_primary , ctx -> renderer .user );
1438- }
1439-
1440- /* Draw label */
1441- float text_x = circle_rect .x + circle_size + ctx -> padding ;
1442- float text_y = ctx -> layout .y + (ctx -> row_height - ctx -> font_height ) * 0.5f ;
1443- iui_internal_draw_text (ctx , text_x , text_y , label , ctx -> colors .on_surface );
1444-
1445- iui_newline (ctx );
1446- return selected ;
1459+ return toggle_base (ctx , label , circle_rect , hit_rect , corner , group_value ,
1460+ button_value , true, radio_draw , NULL );
14471461}
14481462
14491463/* Exposed Dropdown Menu Implementation
@@ -1463,10 +1477,14 @@ bool iui_dropdown(iui_context *ctx, const iui_dropdown_options *options)
14631477 int selected = * options -> selected_index ;
14641478
14651479 /* Clamp selected index to valid range */
1466- if (selected < 0 )
1480+ if (selected < 0 ) {
14671481 selected = 0 ;
1468- if (selected >= options -> option_count )
1482+ * options -> selected_index = selected ;
1483+ }
1484+ if (selected >= options -> option_count ) {
14691485 selected = options -> option_count - 1 ;
1486+ * options -> selected_index = selected ;
1487+ }
14701488
14711489 /* Calculate dropdown field rect */
14721490 float field_h = IUI_DROPDOWN_HEIGHT ;
0 commit comments