Skip to content

Commit 73ea660

Browse files
authored
Merge pull request #11 from sysprog21/toggle-widget
Consolidate toggle widgets (checkbox/radio)
2 parents 65b4926 + a6eba4a commit 73ea660

File tree

1 file changed

+136
-118
lines changed

1 file changed

+136
-118
lines changed

src/input.c

Lines changed: 136 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -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

11351261
bool 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

13701435
bool 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

Comments
 (0)