From 82aefaf758e0203357f7aec6f34901df75da1779 Mon Sep 17 00:00:00 2001 From: Ilia Demianenko Date: Tue, 12 Sep 2023 19:32:14 -0700 Subject: [PATCH] Radio button group --- samples/ui/src/main.c | 24 ++++++- src/ui/ui.c | 147 ++++++++++++++++++++++++++++++++++++++---- src/ui/ui.h | 10 +++ 3 files changed, 165 insertions(+), 16 deletions(-) diff --git a/samples/ui/src/main.c b/samples/ui/src/main.c index aaa8b96..18fe195 100644 --- a/samples/ui/src/main.c +++ b/samples/ui/src/main.c @@ -14,6 +14,7 @@ oc_canvas canvas; oc_font font; oc_ui_context ui; oc_arena textArena = { 0 }; +oc_ui_theme* theme = &OC_UI_DARK_THEME; ORCA_EXPORT void oc_on_init(void) { @@ -86,6 +87,7 @@ void widget_end_view(void) ORCA_EXPORT void oc_on_frame_refresh(void) { oc_arena_scope scratch = oc_scratch_begin(); + oc_ui_set_theme(theme); oc_ui_style defaultStyle = { .font = font }; @@ -184,7 +186,7 @@ ORCA_EXPORT void oc_on_frame_refresh(void) oc_ui_container("up", OC_UI_FLAG_NONE) { - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 0.5 }, + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 0.33 }, .size.height = { OC_UI_SIZE_PARENT, 1 } }, OC_UI_STYLE_SIZE); widget_view("Buttons") @@ -205,7 +207,7 @@ ORCA_EXPORT void oc_on_frame_refresh(void) } } - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 0.5 }, + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 0.33 }, .size.height = { OC_UI_SIZE_PARENT, 1 } }, OC_UI_STYLE_SIZE); @@ -219,6 +221,24 @@ ORCA_EXPORT void oc_on_frame_refresh(void) oc_ui_checkbox("check2", &check2); oc_ui_checkbox("check3", &check3); } + + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 0.33 }, + .size.height = { OC_UI_SIZE_PARENT, 1 } }, + OC_UI_STYLE_SIZE); + + widget_view("Radio group") + { + static int radioSelected = 0; + oc_str8 options[] = { OC_STR8("Dark theme"), + OC_STR8("Light theme") }; + oc_ui_radio_group_info info = { .selectedIndex = radioSelected, + .optionCount = 2, + .options = options }; + + oc_ui_radio_group_info result = oc_ui_radio_group("radio_group", &info); + radioSelected = result.selectedIndex; + theme = radioSelected == 0 ? &OC_UI_DARK_THEME : &OC_UI_LIGHT_THEME; + } } oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X, diff --git a/src/ui/ui.c b/src/ui/ui.c index 9e5302a..638e42e 100644 --- a/src/ui/ui.c +++ b/src/ui/ui.c @@ -15,6 +15,7 @@ #include "util/memory.h" //NOTE(ilia): Design system by semi.design: https://semi.design/en-US/start/overview +// New widgets should support dark and light theme oc_thread_local oc_ui_context oc_uiThreadContext = { 0 }; oc_thread_local oc_ui_context* oc_uiCurrentContext = 0; @@ -538,6 +539,16 @@ bool oc_ui_box_hot(oc_ui_box* box) return (box->hot); } +void oc_ui_box_set_dragging(oc_ui_box* box, bool dragging) +{ + box->dragging = dragging; +} + +bool oc_ui_box_dragging(oc_ui_box* box) +{ + return (box->dragging); +} + oc_ui_sig oc_ui_box_sig(oc_ui_box* box) { //NOTE: compute input signals @@ -1320,20 +1331,12 @@ void oc_ui_draw_box(oc_ui_box* box) oc_ui_style* style = &box->style; bool draw = true; - { oc_rect clip = oc_clip_top(); - oc_rect expRect = { - box->rect.x - 0.5 * style->borderSize, - box->rect.y - 0.5 * style->borderSize, - box->rect.w + style->borderSize, - box->rect.h + style->borderSize - }; - - if((expRect.x + expRect.w < clip.x) - || (expRect.y + expRect.h < clip.y) - || (expRect.x > clip.x + clip.w) - || (expRect.y > clip.y + clip.h)) + if((box->rect.x + box->rect.w < clip.x) + || (box->rect.y + box->rect.h < clip.y) + || (box->rect.x > clip.x + clip.w) + || (box->rect.y > clip.y + clip.h)) { draw = false; } @@ -1344,10 +1347,16 @@ void oc_ui_draw_box(oc_ui_box* box) oc_clip_push(box->rect.x, box->rect.y, box->rect.w, box->rect.h); } + oc_rect insetRect = { .x = box->rect.x + style->borderSize / 2, + .y = box->rect.y + style->borderSize / 2, + .w = box->rect.w - style->borderSize, + .h = box->rect.h - style->borderSize }; + f32 insetRoundness = oc_max(style->roundness - style->borderSize / 2, 0); + if(draw && (box->flags & OC_UI_FLAG_DRAW_BACKGROUND)) { oc_set_color(style->bgColor); - oc_ui_rectangle_fill(box->rect, style->roundness); + oc_ui_rectangle_fill(insetRect, insetRoundness); } if(draw @@ -1416,7 +1425,7 @@ void oc_ui_draw_box(oc_ui_box* box) { oc_set_width(style->borderSize); oc_set_color(style->borderColor); - oc_ui_rectangle_stroke(box->rect, style->roundness); + oc_ui_rectangle_stroke(insetRect, insetRoundness); } } @@ -2668,6 +2677,116 @@ oc_ui_select_popup_info oc_ui_select_popup(const char* name, oc_ui_select_popup_ } oc_ui_box_set_closed(panel, !oc_ui_box_active(panel)); } + result.changed = result.selectedIndex != info->selectedIndex; + return (result); +} + +//------------------------------------------------------------------------------ +// Radio group +//------------------------------------------------------------------------------ + +oc_ui_radio_group_info oc_ui_radio_group(const char* name, oc_ui_radio_group_info* info) +{ + oc_ui_radio_group_info result = *info; + + oc_ui_context* ui = oc_ui_get_context(); + oc_ui_theme* theme = ui->theme; + + oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_Y, + .layout.spacing = 12 }, + OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container(name, 0) + { + for(int i = 0; i < info->optionCount; i++) + { + oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X, + .layout.spacing = 8 }, + OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_box* row = oc_ui_box_begin_str8(info->options[i], OC_UI_FLAG_CLICKABLE); + oc_ui_box* radio = oc_ui_box_make("radio", OC_UI_FLAG_DRAW_BACKGROUND | OC_UI_FLAG_DRAW_BORDER); + + oc_ui_sig sig = oc_ui_box_sig(row); + if(sig.clicked) + { + result.selectedIndex = i; + } + + oc_ui_box_set_hot(radio, sig.hovering); + oc_ui_box_set_dragging(radio, sig.dragging); + + const char* defaultTagStr = "radio"; + const char* selectedTagStr = "radio_selected"; + const char* radioTagStr = result.selectedIndex == i ? selectedTagStr : defaultTagStr; + oc_ui_tag_box(radio, radioTagStr); + + oc_ui_style baseStyle = { .size.width = { OC_UI_SIZE_PIXELS, 16 }, + .size.height = { OC_UI_SIZE_PIXELS, 16 }, + .roundness = 8 }; + oc_ui_style_mask baseMask = OC_UI_STYLE_SIZE + | OC_UI_STYLE_ROUNDNESS; + oc_ui_style_box_before(radio, oc_ui_pattern_owner(), &baseStyle, baseMask); + + oc_ui_tag defaultTag = oc_ui_tag_make(defaultTagStr); + oc_ui_pattern defaultPattern = { 0 }; + oc_ui_pattern_push(&ui->frameArena, &defaultPattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = defaultTag }); + oc_ui_style defaultStyle = { .borderColor = theme->text3, + .borderSize = 1 }; + oc_ui_style_mask defaultMask = OC_UI_STYLE_BORDER_COLOR + | OC_UI_STYLE_BORDER_SIZE; + oc_ui_style_box_before(radio, defaultPattern, &defaultStyle, defaultMask); + + oc_ui_pattern hoverPattern = { 0 }; + oc_ui_pattern_push(&ui->frameArena, &hoverPattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = defaultTag }); + oc_ui_pattern_push(&ui->frameArena, &hoverPattern, (oc_ui_selector){ .op = OC_UI_SEL_AND, .kind = OC_UI_SEL_STATUS, .status = OC_UI_HOVER }); + oc_ui_style hoverStyle = { .bgColor = theme->fill0, + .borderColor = theme->primary }; + oc_ui_style_mask hoverMask = OC_UI_STYLE_BG_COLOR + | OC_UI_STYLE_BORDER_COLOR; + oc_ui_style_box_after(radio, hoverPattern, &hoverStyle, hoverMask); + + oc_ui_pattern draggingPattern = { 0 }; + oc_ui_pattern_push(&ui->frameArena, &draggingPattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = defaultTag }); + oc_ui_pattern_push(&ui->frameArena, &draggingPattern, (oc_ui_selector){ .op = OC_UI_SEL_AND, .kind = OC_UI_SEL_STATUS, .status = OC_UI_DRAGGING }); + oc_ui_style draggingStyle = { .bgColor = theme->fill1, + .borderColor = theme->primary }; + oc_ui_style_mask draggingMask = OC_UI_STYLE_BG_COLOR + | OC_UI_STYLE_BORDER_COLOR; + oc_ui_style_box_after(radio, draggingPattern, &draggingStyle, draggingMask); + + oc_ui_tag selectedTag = oc_ui_tag_make(selectedTagStr); + oc_ui_pattern selectedPattern = { 0 }; + oc_ui_pattern_push(&ui->frameArena, &selectedPattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = selectedTag }); + oc_ui_style selectedStyle = { .bgColor = theme->palette->white, + .borderColor = theme->primary, + .borderSize = 4.6666666666667 }; + oc_ui_style_mask selectedMask = OC_UI_STYLE_BG_COLOR + | OC_UI_STYLE_BORDER_COLOR + | OC_UI_STYLE_BORDER_SIZE; + oc_ui_style_box_before(radio, selectedPattern, &selectedStyle, selectedMask); + + oc_ui_pattern selectedHoverPattern = { 0 }; + oc_ui_pattern_push(&ui->frameArena, &selectedHoverPattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = selectedTag }); + oc_ui_pattern_push(&ui->frameArena, &selectedHoverPattern, (oc_ui_selector){ .op = OC_UI_SEL_AND, .kind = OC_UI_SEL_STATUS, .status = OC_UI_HOVER }); + oc_ui_style selectedHoverStyle = { .borderColor = theme->primaryHover }; + oc_ui_style_box_after(radio, selectedHoverPattern, &selectedHoverStyle, OC_UI_STYLE_BORDER_COLOR); + + oc_ui_pattern selectedDraggingPattern = { 0 }; + oc_ui_pattern_push(&ui->frameArena, &selectedDraggingPattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = selectedTag }); + oc_ui_pattern_push(&ui->frameArena, &selectedDraggingPattern, (oc_ui_selector){ .op = OC_UI_SEL_AND, .kind = OC_UI_SEL_STATUS, .status = OC_UI_DRAGGING }); + oc_ui_style selectedDraggingStyle = { .borderColor = theme->primaryActive }; + oc_ui_style_box_after(radio, selectedDraggingPattern, &selectedDraggingStyle, OC_UI_STYLE_BORDER_COLOR); + + oc_ui_container("label", 0) + { + oc_ui_label_str8(info->options[i]); + } + + oc_ui_box_end(); // row + } + } + result.changed = result.selectedIndex != info->selectedIndex; return (result); } diff --git a/src/ui/ui.h b/src/ui/ui.h index 65e4ace..d2e6d9a 100644 --- a/src/ui/ui.h +++ b/src/ui/ui.h @@ -778,6 +778,16 @@ typedef struct oc_ui_select_popup_info ORCA_API oc_ui_select_popup_info oc_ui_select_popup(const char* name, oc_ui_select_popup_info* info); +typedef struct oc_ui_radio_group_info +{ + bool changed; + int selectedIndex; // -1 if nothing is selected + int optionCount; + oc_str8* options; +} oc_ui_radio_group_info; + +ORCA_API oc_ui_radio_group_info oc_ui_radio_group(const char* name, oc_ui_radio_group_info* info); + #ifdef __cplusplus } // extern "C" #endif