diff --git a/samples/ui/data/OpenSans-Bold.ttf b/samples/ui/data/OpenSans-Bold.ttf new file mode 100644 index 0000000..4a5bc39 Binary files /dev/null and b/samples/ui/data/OpenSans-Bold.ttf differ diff --git a/samples/ui/data/OpenSans-Regular.ttf b/samples/ui/data/OpenSans-Regular.ttf new file mode 100644 index 0000000..29e9e60 Binary files /dev/null and b/samples/ui/data/OpenSans-Regular.ttf differ diff --git a/samples/ui/data/OpenSansLatinSubset.ttf b/samples/ui/data/OpenSansLatinSubset.ttf deleted file mode 100644 index acfe2d8..0000000 Binary files a/samples/ui/data/OpenSansLatinSubset.ttf and /dev/null differ diff --git a/samples/ui/src/main.c b/samples/ui/src/main.c index 18fe195..49c2fe8 100644 --- a/samples/ui/src/main.c +++ b/samples/ui/src/main.c @@ -6,32 +6,47 @@ * **************************************************************************/ #include "orca.h" +#include -oc_vec2 frameSize = { 100, 100 }; +oc_vec2 frameSize = { 1200, 838 }; oc_surface surface; oc_canvas canvas; -oc_font font; +oc_font fontRegular; +oc_font fontBold; oc_ui_context ui; oc_arena textArena = { 0 }; -oc_ui_theme* theme = &OC_UI_DARK_THEME; +oc_arena logArena = { 0 }; +oc_str8_list logLines; + +typedef enum cmd +{ + CMD_NONE, + CMD_SET_DARK_THEME, + CMD_SET_LIGHT_THEME +} cmd; + +cmd command = CMD_NONE; ORCA_EXPORT void oc_on_init(void) { - oc_window_set_title(OC_STR8("ui")); + oc_window_set_title(OC_STR8("Orca UI Demo")); + oc_window_set_size(frameSize); surface = oc_surface_canvas(); canvas = oc_canvas_create(); oc_ui_init(&ui); - //NOTE: load font + oc_font* fonts[2] = { &fontRegular, &fontBold }; + const char* fontNames[2] = { "/OpenSans-Regular.ttf", "/OpenSans-Bold.ttf" }; + for(int i = 0; i < 2; i++) { oc_arena_scope scratch = oc_scratch_begin(); - oc_file file = oc_file_open(OC_STR8("/OpenSansLatinSubset.ttf"), OC_FILE_ACCESS_READ, 0); + oc_file file = oc_file_open(OC_STR8(fontNames[i]), OC_FILE_ACCESS_READ, 0); if(oc_file_last_error(file) != OC_IO_OK) { - oc_log_error("Couldn't open file OpenSansLatinSubset.ttf\n"); + oc_log_error("Couldn't open file %s\n", fontNames[i]); } u64 size = oc_file_size(file); char* buffer = (char*)oc_arena_push(scratch.arena, size); @@ -43,19 +58,14 @@ ORCA_EXPORT void oc_on_init(void) OC_UNICODE_LATIN_EXTENDED_B, OC_UNICODE_SPECIALS }; - font = oc_font_create_from_memory(oc_str8_from_buffer(size, buffer), 5, ranges); + *fonts[i] = oc_font_create_from_memory(oc_str8_from_buffer(size, buffer), 5, ranges); oc_scratch_end(scratch); } oc_arena_init(&textArena); -} - -ORCA_EXPORT void oc_on_resize(u32 width, u32 height) -{ - oc_log_info("frame resize %u, %u", width, height); - frameSize.x = width; - frameSize.y = height; + oc_arena_init(&logArena); + oc_list_init(&logLines.list); } ORCA_EXPORT void oc_on_raw_event(oc_event* event) @@ -63,323 +73,727 @@ ORCA_EXPORT void oc_on_raw_event(oc_event* event) oc_ui_process_event(event); } -void widget_begin_view(char* str) +void log_push(const char* line) { - oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_Y, - .layout.spacing = 10, - .layout.margin.x = 10, - .layout.margin.y = 10, - .layout.align.x = OC_UI_ALIGN_CENTER, - .layout.align.y = OC_UI_ALIGN_START }, - OC_UI_STYLE_LAYOUT); - - oc_ui_box_begin(str, OC_UI_FLAG_DRAW_BORDER); - oc_ui_label(str); + oc_str8_list_push(&logArena, &logLines, (oc_str8)OC_STR8(line)); } -void widget_end_view(void) +void log_pushf(const char* format, ...) { - oc_ui_box_end(); + va_list args; + va_start(args, format); + oc_str8 str = oc_str8_pushfv(&logArena, format, args); + va_end(args); + oc_str8_list_push(&logArena, &logLines, str); } -#define widget_view(s) oc_defer_loop(widget_begin_view(s), widget_end_view()) +void column_begin(const char* header, f32 widthFraction) +{ + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, widthFraction, 1 }, + .size.height = { OC_UI_SIZE_PARENT, 1 }, + .layout.axis = OC_UI_AXIS_Y, + .layout.margin.y = 8, + .layout.spacing = 24, + .bgColor = ui.theme->bg1, + .borderColor = ui.theme->border, + .borderSize = 1, + .roundness = ui.theme->roundnessSmall }, + OC_UI_STYLE_SIZE + | OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_MARGIN_Y + | OC_UI_STYLE_LAYOUT_SPACING + | OC_UI_STYLE_BG_COLOR + | OC_UI_STYLE_BORDER_COLOR + | OC_UI_STYLE_BORDER_SIZE + | OC_UI_STYLE_ROUNDNESS); + oc_ui_box_begin(header, OC_UI_FLAG_DRAW_BACKGROUND | OC_UI_FLAG_DRAW_BORDER); + + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, + .layout.align.x = OC_UI_ALIGN_CENTER }, + OC_UI_STYLE_SIZE_WIDTH + | OC_UI_STYLE_LAYOUT_ALIGN_X); + oc_ui_container("header", OC_UI_FLAG_NONE) + { + oc_ui_style_next(&(oc_ui_style){ .fontSize = 18 }, + OC_UI_STYLE_FONT_SIZE); + oc_ui_label(header); + } + + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, + .size.height = { OC_UI_SIZE_PARENT, 1, 1 }, + .layout.align.x = OC_UI_ALIGN_START, + .layout.margin.x = 16, + .layout.spacing = 24 }, + OC_UI_STYLE_SIZE + | OC_UI_STYLE_LAYOUT_ALIGN_X + | OC_UI_STYLE_LAYOUT_MARGIN_X + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_box_begin("contents", OC_UI_FLAG_NONE); +} + +void column_end() +{ + oc_ui_box_end(); // contents + oc_ui_box_end(); // column +} + +#define column(h, w) oc_defer_loop(column_begin(h, w), column_end()) + +void labeled_slider(const char* label, f32* value) +{ + 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_container(label, OC_UI_FLAG_NONE) + { + oc_ui_style_match_after(oc_ui_pattern_owner(), + &(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 100 } }, + OC_UI_STYLE_SIZE_WIDTH); + oc_ui_label(label); + + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 100 } }, + OC_UI_STYLE_SIZE_WIDTH); + oc_ui_slider("slider", value); + } +} + +ORCA_EXPORT void oc_on_resize(u32 width, u32 height) +{ + frameSize.x = width; + frameSize.y = height; +} + +void reset_next_radio_group_to_dark_theme(oc_arena* arena); 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 }; + switch(command) + { + case CMD_SET_DARK_THEME: + oc_ui_set_theme(&OC_UI_DARK_THEME); + break; + case CMD_SET_LIGHT_THEME: + oc_ui_set_theme(&OC_UI_LIGHT_THEME); + break; + default: + break; + } + command = CMD_NONE; + oc_ui_style defaultStyle = { .font = fontRegular }; oc_ui_style_mask defaultMask = OC_UI_STYLE_FONT; - oc_ui_frame(frameSize, &defaultStyle, defaultMask) { - oc_ui_style_match_before(oc_ui_pattern_all(), &defaultStyle, defaultMask); + //-------------------------------------------------------------------------------------------- + // Menu bar + //-------------------------------------------------------------------------------------------- + oc_ui_menu_bar("menu_bar") + { + oc_ui_menu("File") + { + if(oc_ui_menu_button("Quit").pressed) + { + oc_request_quit(); + } + } - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, - .size.height = { OC_UI_SIZE_PARENT, 1 }, - .layout.axis = OC_UI_AXIS_Y, - .layout.align.x = OC_UI_ALIGN_CENTER, - .layout.align.y = OC_UI_ALIGN_START, - .layout.spacing = 10 }, - OC_UI_STYLE_SIZE - | OC_UI_STYLE_LAYOUT); + oc_ui_menu("Theme") + { + if(oc_ui_menu_button("Dark theme").pressed) + { + command = CMD_SET_DARK_THEME; + } + if(oc_ui_menu_button("Light theme").pressed) + { + command = CMD_SET_LIGHT_THEME; + } + } + } - oc_ui_container("background", OC_UI_FLAG_DRAW_BACKGROUND) + oc_ui_panel("main panel", OC_UI_FLAG_NONE) { - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, - .size.height = { OC_UI_SIZE_CHILDREN }, - .layout.align.x = OC_UI_ALIGN_CENTER, - .layout.margin.x = 10, - .layout.margin.y = 10 }, - OC_UI_STYLE_SIZE - | OC_UI_STYLE_LAYOUT_ALIGN_X - | OC_UI_STYLE_LAYOUT_MARGINS); - oc_ui_container("title", OC_UI_FLAG_NONE) - { - oc_ui_style_next(&(oc_ui_style){ .fontSize = 26 }, OC_UI_STYLE_FONT_SIZE); - oc_ui_label("Orca UI Demo"); - - if(oc_ui_box_sig(oc_ui_box_top()).hovering) - { - oc_ui_tooltip("That is a tooltip!"); - } - } - - oc_ui_menu_bar("Menu bar") - { - oc_ui_menu("Menu 1") - { - if(oc_ui_menu_button("Option 1.1").pressed) - { - oc_log_info("Pressed option 1.1\n"); - } - oc_ui_menu_button("Option 1.2"); - oc_ui_menu_button("Option 1.3"); - oc_ui_menu_button("Option 1.4"); - } - - oc_ui_menu("Menu 2") - { - oc_ui_menu_button("Option 2.1"); - oc_ui_menu_button("Option 2.2"); - oc_ui_menu_button("Option 2.3"); - oc_ui_menu_button("Option 2.4"); - } - - oc_ui_menu("Menu 3") - { - oc_ui_menu_button("Option 3.1"); - oc_ui_menu_button("Option 3.2"); - oc_ui_menu_button("Option 3.3"); - oc_ui_menu_button("Option 3.4"); - } - } - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, .size.height = { OC_UI_SIZE_PARENT, 1, 1 }, - .layout.margin.x = 10, - .layout.margin.y = 10 }, - OC_UI_STYLE_SIZE | OC_UI_STYLE_LAYOUT_MARGINS); + .layout.axis = OC_UI_AXIS_X, + .layout.margin.x = 16, + .layout.margin.y = 16, + .layout.spacing = 16 }, + OC_UI_STYLE_SIZE + | OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_MARGINS + | OC_UI_STYLE_LAYOUT_SPACING); - oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X }, OC_UI_STYLE_LAYOUT_AXIS); - oc_ui_container("contents", OC_UI_FLAG_NONE) + oc_ui_container("background", OC_UI_FLAG_DRAW_BACKGROUND) { - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 0.5 }, - .size.height = { OC_UI_SIZE_PARENT, 1 } }, - OC_UI_STYLE_SIZE); - - oc_ui_container("left", OC_UI_FLAG_NONE) + column("Widgets", 1.0 / 3) { - oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X, - .layout.spacing = 10, - .layout.margin.x = 10, - .layout.margin.y = 10, - .size.width = { OC_UI_SIZE_PARENT, 1 }, - .size.height = { OC_UI_SIZE_PARENT, 0.5 } }, - OC_UI_STYLE_LAYOUT_AXIS - | OC_UI_STYLE_LAYOUT_SPACING - | OC_UI_STYLE_LAYOUT_MARGIN_X - | OC_UI_STYLE_LAYOUT_MARGIN_Y - | OC_UI_STYLE_SIZE); - oc_ui_container("up", OC_UI_FLAG_NONE) + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, + .layout.axis = OC_UI_AXIS_X, + .layout.spacing = 32 }, + OC_UI_STYLE_SIZE_WIDTH + | OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("top", OC_UI_FLAG_NONE) { - 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") + oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_Y, + .layout.spacing = 24 }, + OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("top_left", OC_UI_FLAG_NONE) { - if(oc_ui_button("Button A").clicked) + //----------------------------------------------------------------------------- + // Label + //----------------------------------------------------------------------------- + oc_ui_label("Label"); + + //----------------------------------------------------------------------------- + // Button + //----------------------------------------------------------------------------- + if(oc_ui_button("Button").clicked) { - oc_log_info("A clicked"); + log_push("Button clicked"); } - if(oc_ui_button("Button B").clicked) + oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X, + .layout.align.y = OC_UI_ALIGN_CENTER, + .layout.spacing = 8 }, + OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_ALIGN_Y + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("checkbox", OC_UI_FLAG_NONE) { - oc_log_info("B clicked"); - } + //------------------------------------------------------------------------- + // Checkbox + //------------------------------------------------------------------------- + static bool checked = false; + if(oc_ui_checkbox("checkbox", &checked).clicked) + { + if(checked) + { + log_push("Checkbox checked"); + } + else + { + log_push("Checkbox unchecked"); + } + } - if(oc_ui_button("Button C").clicked) - { - oc_log_info("C clicked"); + oc_ui_label("Checkbox"); } } - 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("checkboxes") + //--------------------------------------------------------------------------------- + // Vertical slider + //--------------------------------------------------------------------------------- + static float vSliderValue = 0; + static float vSliderLoggedValue = 0; + static f64 vSliderLogTime = 0; + oc_ui_style_next(&(oc_ui_style){ .size.height = { OC_UI_SIZE_PIXELS, 130 } }, + OC_UI_STYLE_SIZE_HEIGHT); + oc_ui_slider("v_slider", &vSliderValue); + f64 now = oc_clock_time(OC_CLOCK_MONOTONIC); + if((now - vSliderLogTime) >= 0.2 && vSliderValue != vSliderLoggedValue) { - static bool check1 = true; - static bool check2 = false; - static bool check3 = false; - - oc_ui_checkbox("check1", &check1); - oc_ui_checkbox("check2", &check2); - oc_ui_checkbox("check3", &check3); + log_pushf("Vertical slider moved to %f", vSliderValue); + vSliderLoggedValue = vSliderValue; + vSliderLogTime = now; } - 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") + oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_Y, + .layout.spacing = 24 }, + OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("top_right", OC_UI_FLAG_NONE) { + //----------------------------------------------------------------------------- + // Tooltip + //----------------------------------------------------------------------------- + if(oc_ui_label("Tooltip").hovering) + { + oc_ui_tooltip("Hi"); + } + + //----------------------------------------------------------------------------- + // 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); + oc_str8 options[] = { OC_STR8("Radio 1"), + OC_STR8("Radio 2") }; + oc_ui_radio_group_info radioGroupInfo = { .selectedIndex = radioSelected, + .optionCount = 2, + .options = options }; + oc_ui_radio_group_info result = oc_ui_radio_group("radio_group", &radioGroupInfo); radioSelected = result.selectedIndex; - theme = radioSelected == 0 ? &OC_UI_DARK_THEME : &OC_UI_LIGHT_THEME; + if(result.changed) + { + log_pushf("Selected Radio %i", result.selectedIndex + 1); + } + + //----------------------------------------------------------------------------- + // Horizontal slider + //----------------------------------------------------------------------------- + static float hSliderValue = 0; + static float hSliderLoggedValue = 0; + static f64 hSliderLogTime = 0; + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 130 } }, + OC_UI_STYLE_SIZE_WIDTH); + oc_ui_slider("h_slider", &hSliderValue); + f64 now = oc_clock_time(OC_CLOCK_MONOTONIC); + if((now - hSliderLogTime) >= 0.2 && hSliderValue != hSliderLoggedValue) + { + log_pushf("Slider moved to %f", hSliderValue); + hSliderLoggedValue = hSliderValue; + hSliderLogTime = now; + } } } - oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X, - .size.width = { OC_UI_SIZE_PARENT, 1 }, - .size.height = { OC_UI_SIZE_PARENT, 0.5 } }, - OC_UI_STYLE_LAYOUT_AXIS - | OC_UI_STYLE_SIZE); - - oc_ui_container("down", OC_UI_FLAG_NONE) + //------------------------------------------------------------------------------------- + // Text box + //------------------------------------------------------------------------------------- + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 305 }, + .size.height = { OC_UI_SIZE_TEXT } }, + OC_UI_STYLE_SIZE); + static oc_str8 text = OC_STR8_LIT("Text box"); + oc_ui_text_box_result res = oc_ui_text_box("text", scratch.arena, text); + if(res.changed) { - widget_view("Vertical Sliders") + oc_arena_clear(&textArena); + text = oc_str8_push_copy(&textArena, res.text); + } + if(res.accepted) + { + log_pushf("Entered text \"%s\"", text.ptr); + } + + //------------------------------------------------------------------------------------- + // Select + //------------------------------------------------------------------------------------- + static int selected = -1; + oc_str8 options[] = { OC_STR8("Option 1"), + OC_STR8("Option 2") }; + oc_ui_select_popup_info info = { .selectedIndex = selected, + .optionCount = 2, + .options = options, + .placeholder = OC_STR8_LIT("Select") }; + oc_ui_select_popup_info result = oc_ui_select_popup("select", &info); + if(result.selectedIndex != selected) + { + log_pushf("Selected %s", options[result.selectedIndex].ptr); + } + selected = result.selectedIndex; + + //------------------------------------------------------------------------------------- + // Scrollable panel + //------------------------------------------------------------------------------------- + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, + .size.height = { OC_UI_SIZE_PARENT, 1, 1, .minSize = 200 }, + .bgColor = ui.theme->bg2, + .borderColor = ui.theme->border, + .borderSize = 1, + .roundness = ui.theme->roundnessSmall }, + OC_UI_STYLE_SIZE + | OC_UI_STYLE_BG_COLOR + | OC_UI_STYLE_BORDER_COLOR + | OC_UI_STYLE_BORDER_SIZE + | OC_UI_STYLE_ROUNDNESS); + + oc_ui_panel("log", OC_UI_FLAG_DRAW_BACKGROUND | OC_UI_FLAG_DRAW_BORDER) + { + oc_ui_style_next(&(oc_ui_style){ .layout.margin.x = 16, + .layout.margin.y = 16 }, + OC_UI_STYLE_LAYOUT_MARGINS + | OC_UI_STYLE_LAYOUT_SPACING); + + oc_ui_container("contents", OC_UI_FLAG_NONE) { + if(oc_list_empty(logLines.list)) + { + oc_ui_style_next(&(oc_ui_style){ .color = ui.theme->text2 }, + OC_UI_STYLE_COLOR); + oc_ui_label("Log"); + } + + i32 i = 0; + oc_list_for(logLines.list, logLine, oc_str8_elt, listElt) + { + char id[15]; + snprintf(id, sizeof(id), "%d", i); + oc_ui_container(id, OC_UI_FLAG_NONE) + { + oc_ui_label_str8(logLine->string); + } + i++; + } + } + } + } + + //----------------------------------------------------------------------------------------- + // Styling + //----------------------------------------------------------------------------------------- + // Initial values here are hardcoded from the dark theme and everything is overridden all + // the time. In a real program you'd only override what you need and supply the values from + // ui.theme or ui.theme->palette. + // + // Rule-based styling is described at + // https://www.forkingpaths.dev/posts/23-03-10/rule_based_styling_imgui.html + column("Styling", 2.0 / 3) + { + static f32 unselectedWidth = 16; + static f32 unselectedHeight = 16; + static f32 unselectedRoundness = 8; + static oc_color unselectedBgColor = { 0.086, 0.086, 0.102, 1 }; + static oc_color unselectedBorderColor = { 0.976, 0.976, 0.976, 0.35 }; + static f32 unselectedBorderSize = 1; + static oc_ui_status unselectedWhenStatus = OC_UI_NONE; + + static f32 selectedWidth = 16; + static f32 selectedHeight = 16; + static f32 selectedRoundness = 8; + static oc_color selectedCenterColor = { 1, 1, 1, 1 }; + static oc_color selectedBgColor = { 0.33, 0.66, 1, 1 }; + static oc_ui_status selectedWhenStatus = OC_UI_NONE; + + static oc_color labelFontColor = { 0.976, 0.976, 0.976, 1 }; + static oc_font* labelFont = &fontRegular; + static f32 labelFontSize = 14; + + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, + .size.height = { OC_UI_SIZE_PIXELS, 152 }, + .layout.margin.x = 320, + .layout.margin.y = 16, + .bgColor = OC_UI_DARK_THEME.bg0, + .roundness = OC_UI_DARK_THEME.roundnessSmall }, + OC_UI_STYLE_SIZE + | OC_UI_STYLE_LAYOUT_MARGINS + | OC_UI_STYLE_BG_COLOR + | OC_UI_STYLE_ROUNDNESS); + oc_ui_container("styled_radios", OC_UI_FLAG_DRAW_BACKGROUND | OC_UI_FLAG_DRAW_BORDER) + { + reset_next_radio_group_to_dark_theme(scratch.arena); + + oc_ui_pattern unselectedPattern = { 0 }; + oc_ui_pattern_push(scratch.arena, + &unselectedPattern, + (oc_ui_selector){ .kind = OC_UI_SEL_TAG, + .tag = oc_ui_tag_make("radio") }); + if(unselectedWhenStatus != OC_UI_NONE) + { + oc_ui_pattern_push(scratch.arena, + &unselectedPattern, + (oc_ui_selector){ .op = OC_UI_SEL_AND, + .kind = OC_UI_SEL_STATUS, + .status = unselectedWhenStatus }); + } + oc_ui_style_match_after(unselectedPattern, + &(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, unselectedWidth }, + .size.height = { OC_UI_SIZE_PIXELS, unselectedHeight }, + .bgColor = unselectedBgColor, + .borderColor = unselectedBorderColor, + .borderSize = unselectedBorderSize, + .roundness = unselectedRoundness }, + OC_UI_STYLE_SIZE + | OC_UI_STYLE_BG_COLOR + | OC_UI_STYLE_BORDER_COLOR + | OC_UI_STYLE_BORDER_SIZE + | OC_UI_STYLE_ROUNDNESS); + + oc_ui_pattern selectedPattern = { 0 }; + oc_ui_pattern_push(scratch.arena, + &selectedPattern, + (oc_ui_selector){ .kind = OC_UI_SEL_TAG, + .tag = oc_ui_tag_make("radio_selected") }); + if(selectedWhenStatus != OC_UI_NONE) + { + oc_ui_pattern_push(scratch.arena, + &selectedPattern, + (oc_ui_selector){ .op = OC_UI_SEL_AND, + .kind = OC_UI_SEL_STATUS, + .status = selectedWhenStatus }); + } + oc_ui_style_match_after(selectedPattern, + &(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, selectedWidth }, + .size.height = { OC_UI_SIZE_PIXELS, selectedHeight }, + .color = selectedCenterColor, + .bgColor = selectedBgColor, + .roundness = selectedRoundness }, + OC_UI_STYLE_SIZE + | OC_UI_STYLE_COLOR + | OC_UI_STYLE_BG_COLOR + | OC_UI_STYLE_ROUNDNESS); + + oc_ui_pattern labelPattern = { 0 }; + oc_ui_tag labelTag = oc_ui_tag_make("label"); + oc_ui_pattern_push(scratch.arena, &labelPattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = labelTag }); + oc_ui_style_match_after(labelPattern, + &(oc_ui_style){ .color = labelFontColor, + .font = *labelFont, + .fontSize = labelFontSize }, + OC_UI_STYLE_COLOR + | OC_UI_STYLE_FONT + | OC_UI_STYLE_FONT_SIZE); + + static int selectedIndex = 0; + oc_str8 options[] = { OC_STR8("I"), + OC_STR8("Am"), + OC_STR8("Stylish") }; + oc_ui_radio_group_info radioGroupInfo = { .selectedIndex = selectedIndex, + .optionCount = oc_array_size(options), + .options = options }; + oc_ui_radio_group_info result = oc_ui_radio_group("radio_group", &radioGroupInfo); + selectedIndex = result.selectedIndex; + } + + oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X, + .layout.spacing = 32 }, + OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("controls", OC_UI_FLAG_NONE) + { + oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_Y, + .layout.spacing = 16 }, + OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("unselected", OC_UI_FLAG_NONE) + { + oc_ui_style_next(&(oc_ui_style){ .fontSize = 16 }, + OC_UI_STYLE_FONT_SIZE); + oc_ui_label("Radio style"); + + oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 4 }, + OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("size", OC_UI_FLAG_NONE) + { + f32 widthSlider = (unselectedWidth - 8) / 16; + labeled_slider("Width", &widthSlider); + unselectedWidth = 8 + widthSlider * 16; + + f32 heightSlider = (unselectedHeight - 8) / 16; + labeled_slider("Height", &heightSlider); + unselectedHeight = 8 + heightSlider * 16; + + f32 roundnessSlider = (unselectedRoundness - 4) / 8; + labeled_slider("Roundness", &roundnessSlider); + unselectedRoundness = 4 + roundnessSlider * 8; + } + + oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 4 }, + OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("background", OC_UI_FLAG_NONE) + { + labeled_slider("Background R", &unselectedBgColor.r); + labeled_slider("Background G", &unselectedBgColor.g); + labeled_slider("Background B", &unselectedBgColor.b); + labeled_slider("Background A", &unselectedBgColor.a); + } + + oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 4 }, + OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("border", OC_UI_FLAG_NONE) + { + labeled_slider("Border R", &unselectedBorderColor.r); + labeled_slider("Border G", &unselectedBorderColor.g); + labeled_slider("Border B", &unselectedBorderColor.b); + labeled_slider("Border A", &unselectedBorderColor.a); + } + + f32 borderSizeSlider = unselectedBorderSize / 5; + labeled_slider("Border size", &borderSizeSlider); + unselectedBorderSize = borderSizeSlider * 5; + + oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 10 }, + OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("status_override", OC_UI_FLAG_NONE) + { + oc_ui_label("Override"); + + static int statusIndex = 0; + oc_str8 statusOptions[] = { OC_STR8("Always"), + OC_STR8("When hovering"), + OC_STR8("When active") }; + oc_ui_radio_group_info statusInfo = { .selectedIndex = statusIndex, + .optionCount = oc_array_size(statusOptions), + .options = statusOptions }; + oc_ui_radio_group_info result = oc_ui_radio_group("status", &statusInfo); + statusIndex = result.selectedIndex; + switch(statusIndex) + { + case 0: + unselectedWhenStatus = OC_UI_NONE; + break; + case 1: + unselectedWhenStatus = OC_UI_HOVER; + break; + case 2: + unselectedWhenStatus = OC_UI_ACTIVE; + break; + default: + break; + } + } + } + + oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_Y, + .layout.spacing = 16 }, + OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("selected", OC_UI_FLAG_NONE) + { + oc_ui_style_next(&(oc_ui_style){ .fontSize = 16 }, + OC_UI_STYLE_FONT_SIZE); + oc_ui_label("Radio selected style"); + + oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 4 }, + OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("size", OC_UI_FLAG_NONE) + { + f32 widthSlider = (selectedWidth - 8) / 16; + labeled_slider("Width", &widthSlider); + selectedWidth = 8 + widthSlider * 16; + + f32 heightSlider = (selectedHeight - 8) / 16; + labeled_slider("Height", &heightSlider); + selectedHeight = 8 + heightSlider * 16; + + f32 roundnessSlider = (selectedRoundness - 4) / 8; + labeled_slider("Roundness", &roundnessSlider); + selectedRoundness = 4 + roundnessSlider * 8; + } + + oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 4 }, + OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("color", OC_UI_FLAG_NONE) + { + labeled_slider("Center R", &selectedCenterColor.r); + labeled_slider("Center G", &selectedCenterColor.g); + labeled_slider("Center B", &selectedCenterColor.b); + labeled_slider("Center A", &selectedCenterColor.a); + } + + oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 4 }, + OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("background", OC_UI_FLAG_NONE) + { + labeled_slider("Background R", &selectedBgColor.r); + labeled_slider("Background G", &selectedBgColor.g); + labeled_slider("Background B", &selectedBgColor.b); + labeled_slider("Background A", &selectedBgColor.a); + } + + oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 10 }, + OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("status_override", OC_UI_FLAG_NONE) + { + oc_ui_style_next(&(oc_ui_style){ .size.height = { OC_UI_SIZE_PIXELS, 30 } }, + OC_UI_STYLE_SIZE_HEIGHT); + oc_ui_box_make("spacer", OC_UI_FLAG_NONE); + + oc_ui_label("Override"); + + static int statusIndex = 0; + oc_str8 statusOptions[] = { OC_STR8("Always"), + OC_STR8("When hovering"), + OC_STR8("When active") }; + oc_ui_radio_group_info statusInfo = { .selectedIndex = statusIndex, + .optionCount = oc_array_size(statusOptions), + .options = statusOptions }; + oc_ui_radio_group_info result = oc_ui_radio_group("status", &statusInfo); + statusIndex = result.selectedIndex; + switch(statusIndex) + { + case 0: + selectedWhenStatus = OC_UI_NONE; + break; + case 1: + selectedWhenStatus = OC_UI_HOVER; + break; + case 2: + selectedWhenStatus = OC_UI_ACTIVE; + break; + default: + break; + } + } + } + + oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_Y, + .layout.spacing = 16 }, + OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("label", OC_UI_FLAG_NONE) + { + oc_ui_style_next(&(oc_ui_style){ .fontSize = 16 }, + OC_UI_STYLE_FONT_SIZE); + oc_ui_label("Label style"); + oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X, - .layout.spacing = 10 }, + .layout.spacing = 8 }, OC_UI_STYLE_LAYOUT_AXIS | OC_UI_STYLE_LAYOUT_SPACING); - oc_ui_container("contents", OC_UI_FLAG_NONE) + oc_ui_container("font_color", OC_UI_FLAG_NONE) { - oc_ui_style_next(&(oc_ui_style){ .size.height = { OC_UI_SIZE_PIXELS, 200 } }, - OC_UI_STYLE_SIZE_HEIGHT); - static f32 slider1 = 0; - oc_ui_slider("slider1", &slider1); + oc_ui_style_match_after(oc_ui_pattern_owner(), + &(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 100 } }, + OC_UI_STYLE_SIZE_WIDTH); + oc_ui_label("Font color"); - oc_ui_style_next(&(oc_ui_style){ .size.height = { OC_UI_SIZE_PIXELS, 200 } }, - OC_UI_STYLE_SIZE_HEIGHT); - static f32 slider2 = 0; - oc_ui_slider("slider2", &slider2); - - oc_ui_style_next(&(oc_ui_style){ .size.height = { OC_UI_SIZE_PIXELS, 200 } }, - OC_UI_STYLE_SIZE_HEIGHT); - static f32 slider3 = 0; - oc_ui_slider("slider3", &slider3); + static int colorSelected = 0; + oc_str8 colorNames[] = { OC_STR8("Default"), + OC_STR8("Red"), + OC_STR8("Orange"), + OC_STR8("Amber"), + OC_STR8("Yellow"), + OC_STR8("Lime"), + OC_STR8("Light Green"), + OC_STR8("Green") }; + oc_color colors[] = { OC_UI_DARK_THEME.text0, + OC_UI_DARK_THEME.palette->red5, + OC_UI_DARK_THEME.palette->orange5, + OC_UI_DARK_THEME.palette->amber5, + OC_UI_DARK_THEME.palette->yellow5, + OC_UI_DARK_THEME.palette->lime5, + OC_UI_DARK_THEME.palette->lightGreen5, + OC_UI_DARK_THEME.palette->green5 }; + oc_ui_select_popup_info colorInfo = { .selectedIndex = colorSelected, + .optionCount = oc_array_size(colorNames), + .options = colorNames }; + oc_ui_select_popup_info colorResult = oc_ui_select_popup("color", &colorInfo); + colorSelected = colorResult.selectedIndex; + labelFontColor = colors[colorSelected]; } - } - widget_view("Horizontal Sliders") - { - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 200 } }, - OC_UI_STYLE_SIZE_WIDTH); - static f32 slider1 = 0; - oc_ui_slider("slider1", &slider1); - - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 200 } }, - OC_UI_STYLE_SIZE_WIDTH); - static f32 slider2 = 0; - oc_ui_slider("slider2", &slider2); - - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 200 } }, - OC_UI_STYLE_SIZE_WIDTH); - static f32 slider3 = 0; - oc_ui_slider("slider3", &slider3); - } - } - } - - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 0.5 }, - .size.height = { OC_UI_SIZE_PARENT, 1 } }, - OC_UI_STYLE_SIZE); - - oc_ui_container("right", 0) - { - - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, - .size.height = { OC_UI_SIZE_PARENT, 0.33 } }, - OC_UI_STYLE_SIZE); - widget_view("Text box") - { - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 300 }, - .size.height = { OC_UI_SIZE_TEXT } }, - OC_UI_STYLE_SIZE); - static oc_str8 text = { 0 }; - oc_ui_text_box_result res = oc_ui_text_box("textbox", scratch.arena, text); - if(res.changed) - { - oc_arena_clear(&textArena); - text = oc_str8_push_copy(&textArena, res.text); - } - } - - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, - .size.height = { OC_UI_SIZE_PARENT, 0.33 } }, - OC_UI_STYLE_SIZE); - widget_view("Test") - { - static int selected = 0; - oc_str8 options[] = { OC_STR8("Option 1"), - OC_STR8("Option 2"), - OC_STR8("Long option 3"), - OC_STR8("Option 4"), - OC_STR8("Option 5") }; - oc_ui_select_popup_info info = { .selectedIndex = selected, - .optionCount = 5, - .options = options }; - - oc_ui_select_popup_info result = oc_ui_select_popup("popup", &info); - selected = result.selectedIndex; - } - - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, - .size.height = { OC_UI_SIZE_PARENT, 0.33 } }, - OC_UI_STYLE_SIZE); - widget_view("Color") - { - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, - .size.height = { OC_UI_SIZE_PARENT, 0.7 }, - .layout.axis = OC_UI_AXIS_X }, - OC_UI_STYLE_SIZE - | OC_UI_STYLE_LAYOUT_AXIS); - - oc_ui_panel("Panel", OC_UI_FLAG_DRAW_BORDER) - { - oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X }, - OC_UI_STYLE_LAYOUT_AXIS); - oc_ui_container("contents", 0) + 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_container("font", OC_UI_FLAG_NONE) { - oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 20 }, - OC_UI_STYLE_LAYOUT_SPACING); - oc_ui_container("buttons", 0) - { - if(oc_ui_button("Break the law").clicked) - { - oc_clipboard_set_string((oc_str8)OC_STR8_LIT("Check out my cool website")); - } - oc_ui_button("Button B"); - oc_ui_button("Button C"); - oc_ui_button("Button D"); - } + oc_ui_style_match_after(oc_ui_pattern_owner(), + &(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 100 } }, + OC_UI_STYLE_SIZE_WIDTH); + oc_ui_label("Font"); - oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X, - .layout.spacing = 20 }, - OC_UI_STYLE_LAYOUT_SPACING - | OC_UI_STYLE_LAYOUT_AXIS); - - oc_ui_container("buttons2", 0) - { - oc_ui_button("Button A"); - oc_ui_button("Button B"); - oc_ui_button("Button C"); - oc_ui_button("Button D"); - } + static int fontSelected = 0; + oc_str8 fontNames[] = { OC_STR8("Regular"), + OC_STR8("Bold") }; + oc_font* fonts[] = { &fontRegular, + &fontBold }; + oc_ui_select_popup_info fontInfo = { .selectedIndex = fontSelected, + .optionCount = oc_array_size(fontNames), + .options = fontNames }; + oc_ui_select_popup_info fontResult = oc_ui_select_popup("font_style", &fontInfo); + fontSelected = fontResult.selectedIndex; + labelFont = fonts[fontSelected]; } + + f32 fontSizeSlider = (labelFontSize - 8) / 16; + labeled_slider("Font size", &fontSizeSlider); + labelFontSize = 8 + fontSizeSlider * 16; } } } @@ -389,9 +803,66 @@ ORCA_EXPORT void oc_on_frame_refresh(void) oc_canvas_select(canvas); oc_surface_select(surface); + + oc_set_color(ui.theme->bg0); + oc_clear(); + oc_ui_draw(); oc_render(canvas); oc_surface_present(surface); oc_scratch_end(scratch); } + +// This makes sure the light theme doesn't break the styling overrides +// You won't need it in a real program as long as your colors come from ui.theme or ui.theme->palette +void reset_next_radio_group_to_dark_theme(oc_arena* arena) +{ + oc_ui_tag defaultTag = oc_ui_tag_make("radio"); + oc_ui_pattern defaultPattern = { 0 }; + oc_ui_pattern_push(arena, &defaultPattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = defaultTag }); + oc_ui_style defaultStyle = { .borderColor = OC_UI_DARK_THEME.text3, + .borderSize = 1 }; + oc_ui_style_mask defaultMask = OC_UI_STYLE_BORDER_COLOR + | OC_UI_STYLE_BORDER_SIZE; + oc_ui_style_match_after(defaultPattern, &defaultStyle, defaultMask); + + oc_ui_pattern hoverPattern = { 0 }; + oc_ui_pattern_push(arena, &hoverPattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = defaultTag }); + oc_ui_pattern_push(arena, &hoverPattern, (oc_ui_selector){ .op = OC_UI_SEL_AND, .kind = OC_UI_SEL_STATUS, .status = OC_UI_HOVER }); + oc_ui_style hoverStyle = { .bgColor = OC_UI_DARK_THEME.fill0, + .borderColor = OC_UI_DARK_THEME.primary }; + oc_ui_style_mask hoverMask = OC_UI_STYLE_BG_COLOR + | OC_UI_STYLE_BORDER_COLOR; + oc_ui_style_match_after(hoverPattern, &hoverStyle, hoverMask); + + oc_ui_pattern activePattern = { 0 }; + oc_ui_pattern_push(arena, &activePattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = defaultTag }); + oc_ui_pattern_push(arena, &activePattern, (oc_ui_selector){ .op = OC_UI_SEL_AND, .kind = OC_UI_SEL_STATUS, .status = OC_UI_ACTIVE }); + oc_ui_style activeStyle = { .bgColor = OC_UI_DARK_THEME.fill1, + .borderColor = OC_UI_DARK_THEME.primary }; + oc_ui_style_mask activeMask = OC_UI_STYLE_BG_COLOR + | OC_UI_STYLE_BORDER_COLOR; + oc_ui_style_match_after(activePattern, &activeStyle, activeMask); + + oc_ui_tag selectedTag = oc_ui_tag_make("radio_selected"); + oc_ui_pattern selectedPattern = { 0 }; + oc_ui_pattern_push(arena, &selectedPattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = selectedTag }); + oc_ui_style selectedStyle = { .color = OC_UI_DARK_THEME.palette->white, + .bgColor = OC_UI_DARK_THEME.primary }; + oc_ui_style_mask selectedMask = OC_UI_STYLE_COLOR + | OC_UI_STYLE_BG_COLOR; + oc_ui_style_match_after(selectedPattern, &selectedStyle, selectedMask); + + oc_ui_pattern selectedHoverPattern = { 0 }; + oc_ui_pattern_push(arena, &selectedHoverPattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = selectedTag }); + oc_ui_pattern_push(arena, &selectedHoverPattern, (oc_ui_selector){ .op = OC_UI_SEL_AND, .kind = OC_UI_SEL_STATUS, .status = OC_UI_HOVER }); + oc_ui_style selectedHoverStyle = { .bgColor = OC_UI_DARK_THEME.primaryHover }; + oc_ui_style_match_after(selectedHoverPattern, &selectedHoverStyle, OC_UI_STYLE_BG_COLOR); + + oc_ui_pattern selectedActivePattern = { 0 }; + oc_ui_pattern_push(arena, &selectedActivePattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = selectedTag }); + oc_ui_pattern_push(arena, &selectedActivePattern, (oc_ui_selector){ .op = OC_UI_SEL_AND, .kind = OC_UI_SEL_STATUS, .status = OC_UI_ACTIVE }); + oc_ui_style selectedActiveStyle = { .bgColor = OC_UI_DARK_THEME.primaryActive }; + oc_ui_style_match_after(selectedActivePattern, &selectedActiveStyle, OC_UI_STYLE_BG_COLOR); +} diff --git a/sketches/ui/OpenSans-Bold.ttf b/sketches/ui/OpenSans-Bold.ttf new file mode 100644 index 0000000..4a5bc39 Binary files /dev/null and b/sketches/ui/OpenSans-Bold.ttf differ diff --git a/sketches/ui/OpenSans-Regular.ttf b/sketches/ui/OpenSans-Regular.ttf new file mode 100644 index 0000000..29e9e60 Binary files /dev/null and b/sketches/ui/OpenSans-Regular.ttf differ diff --git a/sketches/ui/main.c b/sketches/ui/main.c index aaec438..082ea7b 100644 --- a/sketches/ui/main.c +++ b/sketches/ui/main.c @@ -15,6 +15,7 @@ #include "orca.h" +/* void debug_print_indent(int indent) { for(int i = 0; i < indent; i++) @@ -148,110 +149,124 @@ void debug_print_styles(oc_ui_box* box, int indent) } } } +*/ -oc_font create_font() +oc_vec2 frameSize = { 1200, 838 }; + +oc_surface surface; +oc_canvas canvas; +oc_font fontRegular; +oc_font fontBold; +oc_ui_context ui; +oc_arena textArena = { 0 }; +oc_arena logArena = { 0 }; +oc_str8_list logLines; + +typedef enum cmd { - //NOTE(martin): create font - oc_arena_scope scratch = oc_scratch_begin(); - oc_str8 fontPath = oc_path_executable_relative(scratch.arena, OC_STR8("../../resources/OpenSansLatinSubset.ttf")); - char* fontPathCString = oc_str8_to_cstring(scratch.arena, fontPath); + CMD_NONE, + CMD_SET_DARK_THEME, + CMD_SET_LIGHT_THEME +} cmd; - FILE* fontFile = fopen(fontPathCString, "r"); - if(!fontFile) +cmd command = CMD_NONE; + +void log_push(const char* line) +{ + oc_str8_list_push(&logArena, &logLines, (oc_str8)OC_STR8(line)); +} + +void log_pushf(const char* format, ...) +{ + va_list args; + va_start(args, format); + oc_str8 str = oc_str8_pushfv(&logArena, format, args); + va_end(args); + oc_str8_list_push(&logArena, &logLines, str); +} + +void column_begin(const char* header, f32 widthFraction) +{ + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, widthFraction }, + .size.height = { OC_UI_SIZE_PARENT, 1 }, + .layout.axis = OC_UI_AXIS_Y, + .layout.margin.y = 8, + .layout.spacing = 24, + .bgColor = ui.theme->bg1, + .borderColor = ui.theme->border, + .borderSize = 1, + .roundness = ui.theme->roundnessSmall }, + OC_UI_STYLE_SIZE + | OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_MARGIN_Y + | OC_UI_STYLE_LAYOUT_SPACING + | OC_UI_STYLE_BG_COLOR + | OC_UI_STYLE_BORDER_COLOR + | OC_UI_STYLE_BORDER_SIZE + | OC_UI_STYLE_ROUNDNESS); + oc_ui_box_begin(header, OC_UI_FLAG_DRAW_BACKGROUND | OC_UI_FLAG_DRAW_BORDER); + + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, + .layout.align.x = OC_UI_ALIGN_CENTER }, + OC_UI_STYLE_SIZE_WIDTH + | OC_UI_STYLE_LAYOUT_ALIGN_X); + oc_ui_container("header", OC_UI_FLAG_NONE) { - oc_log_error("Could not load font file '%s': %s\n", fontPathCString, strerror(errno)); - oc_scratch_end(scratch); - return (oc_font_nil()); - } - unsigned char* fontData = 0; - fseek(fontFile, 0, SEEK_END); - u32 fontDataSize = ftell(fontFile); - rewind(fontFile); - fontData = (unsigned char*)malloc(fontDataSize); - fread(fontData, 1, fontDataSize, fontFile); - fclose(fontFile); - - oc_unicode_range ranges[5] = { OC_UNICODE_BASIC_LATIN, - OC_UNICODE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT, - OC_UNICODE_LATIN_EXTENDED_A, - OC_UNICODE_LATIN_EXTENDED_B, - OC_UNICODE_SPECIALS }; - - oc_font font = oc_font_create_from_memory(oc_str8_from_buffer(fontDataSize, (char*)fontData), 5, ranges); - free(fontData); - - oc_scratch_end(scratch); - return (font); -} - -void widget_begin_view(char* str) -{ - oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_Y, - .layout.spacing = 10, - .layout.margin.x = 10, - .layout.margin.y = 10, - .layout.align.x = OC_UI_ALIGN_CENTER, - .layout.align.y = OC_UI_ALIGN_START }, - OC_UI_STYLE_LAYOUT); - - oc_ui_box_begin(str, OC_UI_FLAG_DRAW_BORDER); - oc_ui_label(str); -} - -void widget_end_view(void) -{ - oc_ui_box_end(); -} - -#define widget_view(s) oc_defer_loop(widget_begin_view(s), widget_end_view()) - -int main() -{ - oc_init(); - oc_clock_init(); //TODO put that in oc_init()? - - oc_ui_context context; - oc_ui_init(&context); - oc_ui_set_context(&context); - - oc_rect windowRect = { .x = 100, .y = 100, .w = 810, .h = 610 }; - oc_window window = oc_window_create(windowRect, OC_STR8("test"), 0); - - oc_rect contentRect = oc_window_get_content_rect(window); - - //NOTE: create surface - oc_surface surface = oc_surface_create_for_window(window, OC_CANVAS); - oc_surface_swap_interval(surface, 0); - - //TODO: create canvas - oc_canvas canvas = oc_canvas_create(); - - if(oc_canvas_is_nil(canvas)) - { - oc_log_error("Error: couldn't create canvas\n"); - return (-1); + oc_ui_style_next(&(oc_ui_style){ .fontSize = 18 }, + OC_UI_STYLE_FONT_SIZE); + oc_ui_label(header); } - oc_font font = create_font(); + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, + .size.height = { OC_UI_SIZE_PARENT, 1, 1 }, + .layout.align.x = OC_UI_ALIGN_START, + .layout.margin.x = 16, + .layout.spacing = 24 }, + OC_UI_STYLE_SIZE + | OC_UI_STYLE_LAYOUT_ALIGN_X + | OC_UI_STYLE_LAYOUT_MARGIN_X + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_box_begin("contents", OC_UI_FLAG_NONE | OC_UI_FLAG_DRAW_BORDER); +} - oc_arena textArena = { 0 }; - oc_arena_init(&textArena); +void column_end() +{ + oc_ui_box_end(); // contents + oc_ui_box_end(); // column +} - // start app - oc_window_bring_to_front(window); - oc_window_focus(window); +#define column(h, w) oc_defer_loop(column_begin(h, w), column_end()) + +void labeled_slider(const char* label, f32* value) +{ + 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_container(label, OC_UI_FLAG_NONE) + { + oc_ui_style_match_after(oc_ui_pattern_owner(), + &(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 100 } }, + OC_UI_STYLE_SIZE_WIDTH); + oc_ui_label(label); + + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 100 } }, + OC_UI_STYLE_SIZE_WIDTH); + oc_ui_slider("slider", value); + } +} + +i32 ui_runloop(void* user) +{ + canvas = oc_canvas_create(); + oc_ui_init(&ui); while(!oc_should_quit()) { oc_arena_scope scratch = oc_scratch_begin(); - bool printDebugStyle = false; - - f64 startTime = oc_clock_time(OC_CLOCK_MONOTONIC); - - oc_pump_events(0); oc_event* event = 0; - while((event = oc_next_event(scratch)) != 0) + while((event = oc_next_event(scratch.arena)) != 0) { oc_ui_process_event(event); @@ -263,12 +278,9 @@ int main() } break; - case OC_EVENT_KEYBOARD_KEY: + case OC_EVENT_WINDOW_RESIZE: { - if(event->key.action == OC_KEY_PRESS && event->key.code == OC_KEY_P) - { - printDebugStyle = true; - } + frameSize = (oc_vec2){ event->move.content.w, event->move.content.h }; } break; @@ -277,368 +289,711 @@ int main() } } - //TEST UI - oc_ui_style defaultStyle = { .bgColor = { 0 }, - .color = { 1, 1, 1, 1 }, - .font = font, - .fontSize = 16, - .borderColor = { 1, 0, 0, 1 }, - .borderSize = 2 }; - - oc_ui_style_mask defaultMask = OC_UI_STYLE_BG_COLOR - | OC_UI_STYLE_COLOR - | OC_UI_STYLE_BORDER_COLOR - | OC_UI_STYLE_BORDER_SIZE - | OC_UI_STYLE_FONT - | OC_UI_STYLE_FONT_SIZE; - - oc_ui_flags debugFlags = OC_UI_FLAG_DRAW_BORDER; - - oc_ui_box* root = 0; - - oc_vec2 frameSize = oc_surface_get_size(surface); + switch(command) + { + case CMD_SET_DARK_THEME: + oc_ui_set_theme(&OC_UI_DARK_THEME); + break; + case CMD_SET_LIGHT_THEME: + oc_ui_set_theme(&OC_UI_LIGHT_THEME); + break; + default: + break; + } + command = CMD_NONE; + oc_ui_style defaultStyle = { .font = fontRegular }; + oc_ui_style_mask defaultMask = OC_UI_STYLE_FONT; oc_ui_frame(frameSize, &defaultStyle, defaultMask) { - root = oc_ui_box_top(); - oc_ui_style_match_before(oc_ui_pattern_all(), &defaultStyle, defaultMask); + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1, .minSize = 1200 }, + .size.height = { OC_UI_SIZE_PARENT, 1, .minSize = 838 } }, + OC_UI_STYLE_SIZE); - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, - .size.height = { OC_UI_SIZE_PARENT, 1 }, - .layout.axis = OC_UI_AXIS_Y, - .layout.align.x = OC_UI_ALIGN_CENTER, - .layout.align.y = OC_UI_ALIGN_START, - .layout.spacing = 10, - .layout.margin.x = 10, - .layout.margin.y = 10, - .bgColor = { 0.11, 0.11, 0.11, 1 } }, - OC_UI_STYLE_SIZE - | OC_UI_STYLE_LAYOUT - | OC_UI_STYLE_BG_COLOR); - - oc_ui_container("background", OC_UI_FLAG_DRAW_BACKGROUND) + oc_ui_container("top level", OC_UI_FLAG_NONE) { + //-------------------------------------------------------------------------------------------- + // Menu bar + //-------------------------------------------------------------------------------------------- + oc_ui_menu_bar("menu_bar") + { + oc_ui_menu("File") + { + if(oc_ui_menu_button("Quit").pressed) + { + oc_request_quit(); + } + } + + oc_ui_menu("Theme") + { + if(oc_ui_menu_button("Dark theme").pressed) + { + command = CMD_SET_DARK_THEME; + } + if(oc_ui_menu_button("Light theme").pressed) + { + command = CMD_SET_LIGHT_THEME; + } + } + } + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, - .size.height = { OC_UI_SIZE_CHILDREN }, - .layout.align.x = OC_UI_ALIGN_CENTER }, + .size.height = { OC_UI_SIZE_PARENT, 1, 1 }, + .layout.axis = OC_UI_AXIS_X, + .layout.margin.x = 16, + .layout.margin.y = 16, + .layout.spacing = 16 }, OC_UI_STYLE_SIZE - | OC_UI_STYLE_LAYOUT_ALIGN_X); - oc_ui_container("title", debugFlags) + | OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_MARGINS + | OC_UI_STYLE_LAYOUT_SPACING); + + oc_ui_container("background", OC_UI_FLAG_DRAW_BACKGROUND) { - oc_ui_style_next(&(oc_ui_style){ .fontSize = 26 }, OC_UI_STYLE_FONT_SIZE); - oc_ui_label("Orca UI Demo"); - - if(oc_ui_box_sig(oc_ui_box_top()).hovering) + column("Widgets", 1.0 / 3) { - oc_ui_tooltip("tooltip") - { - oc_ui_style_next(&(oc_ui_style){ .bgColor = { 1, 0.99, 0.82, 1 } }, - OC_UI_STYLE_BG_COLOR); - oc_ui_container("background", OC_UI_FLAG_DRAW_BACKGROUND) + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, + .layout.axis = OC_UI_AXIS_X, + .layout.spacing = 32 }, + OC_UI_STYLE_SIZE_WIDTH + | OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("top", OC_UI_FLAG_NONE) + { + oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_Y, + .layout.spacing = 24 }, + OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("top_left", OC_UI_FLAG_NONE) { - oc_ui_style_next(&(oc_ui_style){ .color = { 0, 0, 0, 1 } }, - OC_UI_STYLE_COLOR); + //----------------------------------------------------------------------------- + // Label + //----------------------------------------------------------------------------- + oc_ui_label("Label"); - oc_ui_label("That is a tooltip!"); - } - } - } - } - - oc_ui_menu_bar("Menu bar") - { - oc_ui_menu("Menu 1") - { - if(oc_ui_menu_button("Option 1.1").pressed) - { - oc_log_info("Pressed option 1.1\n"); - } - oc_ui_menu_button("Option 1.2"); - oc_ui_menu_button("Option 1.3"); - oc_ui_menu_button("Option 1.4"); - } - - oc_ui_menu("Menu 2") - { - oc_ui_menu_button("Option 2.1"); - oc_ui_menu_button("Option 2.2"); - oc_ui_menu_button("Option 2.3"); - oc_ui_menu_button("Option 2.4"); - } - - oc_ui_menu("Menu 3") - { - oc_ui_menu_button("Option 3.1"); - oc_ui_menu_button("Option 3.2"); - oc_ui_menu_button("Option 3.3"); - oc_ui_menu_button("Option 3.4"); - } - } - - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, - .size.height = { OC_UI_SIZE_PARENT, 1, 1 } }, - OC_UI_STYLE_SIZE); - - oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X }, OC_UI_STYLE_LAYOUT_AXIS); - oc_ui_container("contents", debugFlags) - { - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 0.5 }, - .size.height = { OC_UI_SIZE_PARENT, 1 }, - .borderColor = { 0, 0, 1, 1 } }, - OC_UI_STYLE_SIZE - | OC_UI_STYLE_BORDER_COLOR); - - oc_ui_container("left", debugFlags) - { - oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X, - .layout.spacing = 10, - .layout.margin.x = 10, - .layout.margin.y = 10, - .size.width = { OC_UI_SIZE_PARENT, 1 }, - .size.height = { OC_UI_SIZE_PARENT, 0.5 } }, - OC_UI_STYLE_LAYOUT_AXIS - | OC_UI_STYLE_LAYOUT_SPACING - | OC_UI_STYLE_LAYOUT_MARGIN_X - | OC_UI_STYLE_LAYOUT_MARGIN_Y - | OC_UI_STYLE_SIZE); - - oc_ui_container("up", debugFlags) - { - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 0.5 }, - .size.height = { OC_UI_SIZE_PARENT, 1 } }, - OC_UI_STYLE_SIZE); - widget_view("Buttons") - { - if(oc_ui_button("Test Dialog").clicked) + //----------------------------------------------------------------------------- + // Button + //----------------------------------------------------------------------------- + if(oc_ui_button("Button").clicked) { - static oc_str8 options_strings[] = { - OC_STR8_LIT("Accept"), - OC_STR8_LIT("Reject"), - }; - - oc_str8_list options = { 0 }; - oc_str8_list_push(scratch, &options, options_strings[0]); - oc_str8_list_push(scratch, &options, options_strings[1]); - - int res = oc_alert_popup(OC_STR8("test dialog"), OC_STR8("dialog message"), options); - if(res >= 0) - { - oc_log_info("selected options %i: %s\n", res, options_strings[res].ptr); - } - else - { - oc_log_info("no options selected\n"); - } + log_push("Button clicked"); } - if(oc_ui_button("Open").clicked) - { - oc_str8_list filters = { 0 }; - oc_str8_list_push(scratch, &filters, OC_STR8("md")); - - oc_str8 file = oc_open_dialog(scratch, OC_STR8("Open File"), OC_STR8("C:\\Users"), filters, false); - oc_log_info("selected file %.*s\n", (int)file.len, file.ptr); - } - - if(oc_ui_button("Save").clicked) - { - oc_str8_list filters = { 0 }; - - oc_str8 file = oc_save_dialog(scratch, OC_STR8("Save File"), OC_STR8("C:\\Users"), filters); - oc_log_info("selected file %.*s\n", (int)file.len, file.ptr); - } - } - - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 0.5 }, - .size.height = { OC_UI_SIZE_PARENT, 1 } }, - OC_UI_STYLE_SIZE); - - oc_ui_pattern pattern = { 0 }; - oc_ui_pattern_push(scratch, &pattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = oc_ui_tag_make("checkbox") }); - oc_ui_style_match_after(pattern, - &(oc_ui_style){ .bgColor = { 0, 1, 0, 1 }, - .color = { 1, 1, 1, 1 } }, - OC_UI_STYLE_COLOR | OC_UI_STYLE_BG_COLOR); - - widget_view("checkboxes") - { - static bool check1 = true; - static bool check2 = false; - static bool check3 = false; - - oc_ui_checkbox("check1", &check1); - oc_ui_checkbox("check2", &check2); - oc_ui_checkbox("check3", &check3); - } - } - - oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X, - .size.width = { OC_UI_SIZE_PARENT, 1 }, - .size.height = { OC_UI_SIZE_PARENT, 0.5 } }, - OC_UI_STYLE_LAYOUT_AXIS - | OC_UI_STYLE_SIZE); - - oc_ui_container("down", debugFlags) - { - widget_view("Vertical Sliders") - { oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X, - .layout.spacing = 10 }, + .layout.align.y = OC_UI_ALIGN_CENTER, + .layout.spacing = 8 }, + OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_ALIGN_Y + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("checkbox", OC_UI_FLAG_NONE) + { + //------------------------------------------------------------------------- + // Checkbox + //------------------------------------------------------------------------- + static bool checked = false; + if(oc_ui_checkbox("checkbox", &checked).clicked) + { + if(checked) + { + log_push("Checkbox checked"); + } + else + { + log_push("Checkbox unchecked"); + } + } + + oc_ui_label("Checkbox"); + } + } + + //--------------------------------------------------------------------------------- + // Vertical slider + //--------------------------------------------------------------------------------- + static float vSliderValue = 0; + static float vSliderLoggedValue = 0; + static f64 vSliderLogTime = 0; + oc_ui_style_next(&(oc_ui_style){ .size.height = { OC_UI_SIZE_PIXELS, 130 } }, + OC_UI_STYLE_SIZE_HEIGHT); + oc_ui_slider("v_slider", &vSliderValue); + f64 now = oc_clock_time(OC_CLOCK_MONOTONIC); + if((now - vSliderLogTime) >= 0.2 && vSliderValue != vSliderLoggedValue) + { + log_pushf("Vertical slider moved to %f", vSliderValue); + vSliderLoggedValue = vSliderValue; + vSliderLogTime = now; + } + + oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_Y, + .layout.spacing = 24 }, + OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("top_right", OC_UI_FLAG_NONE) + { + //----------------------------------------------------------------------------- + // Tooltip + //----------------------------------------------------------------------------- + if(oc_ui_label("Tooltip").hovering) + { + oc_ui_tooltip("Hi"); + } + + //----------------------------------------------------------------------------- + // Radio group + //----------------------------------------------------------------------------- + static int radioSelected = 0; + oc_str8 options[] = { OC_STR8("Radio 1"), + OC_STR8("Radio 2") }; + oc_ui_radio_group_info radioGroupInfo = { .selectedIndex = radioSelected, + .optionCount = 2, + .options = options }; + oc_ui_radio_group_info result = oc_ui_radio_group("radio_group", &radioGroupInfo); + radioSelected = result.selectedIndex; + if(result.changed) + { + log_pushf("Selected Radio %i", result.selectedIndex + 1); + } + + //----------------------------------------------------------------------------- + // Horizontal slider + //----------------------------------------------------------------------------- + static float hSliderValue = 0; + static float hSliderLoggedValue = 0; + static f64 hSliderLogTime = 0; + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 130 } }, + OC_UI_STYLE_SIZE_WIDTH); + oc_ui_slider("h_slider", &hSliderValue); + f64 now = oc_clock_time(OC_CLOCK_MONOTONIC); + if((now - hSliderLogTime) >= 0.2 && hSliderValue != hSliderLoggedValue) + { + log_pushf("Slider moved to %f", hSliderValue); + hSliderLoggedValue = hSliderValue; + hSliderLogTime = now; + } + } + } + + //------------------------------------------------------------------------------------- + // Text box + //------------------------------------------------------------------------------------- + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 305 }, + .size.height = { OC_UI_SIZE_TEXT } }, + OC_UI_STYLE_SIZE); + static oc_str8 text = OC_STR8_LIT("Text box"); + oc_ui_text_box_result res = oc_ui_text_box("text", scratch.arena, text); + if(res.changed) + { + oc_arena_clear(&textArena); + text = oc_str8_push_copy(&textArena, res.text); + } + if(res.accepted) + { + log_pushf("Entered text \"%s\"", text.ptr); + } + + //------------------------------------------------------------------------------------- + // Select + //------------------------------------------------------------------------------------- + static int selected = -1; + oc_str8 options[] = { OC_STR8("Option 1"), + OC_STR8("Option 2") }; + oc_ui_select_popup_info info = { .selectedIndex = selected, + .optionCount = 2, + .options = options, + .placeholder = OC_STR8_LIT("Select") }; + oc_ui_select_popup_info result = oc_ui_select_popup("select", &info); + if(result.selectedIndex != selected) + { + log_pushf("Selected %s", options[result.selectedIndex].ptr); + } + selected = result.selectedIndex; + + //------------------------------------------------------------------------------------- + // Scrollable panel + //------------------------------------------------------------------------------------- + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, + .size.height = { OC_UI_SIZE_PARENT, 1, 1 }, + .layout.margin.x = 16, + .layout.margin.y = 16, + .layout.spacing = 8, + .bgColor = ui.theme->bg2, + .borderColor = ui.theme->border, + .borderSize = 1, + .roundness = ui.theme->roundnessSmall }, + OC_UI_STYLE_SIZE + | OC_UI_STYLE_LAYOUT_MARGINS + | OC_UI_STYLE_LAYOUT_SPACING + | OC_UI_STYLE_BG_COLOR + | OC_UI_STYLE_BORDER_COLOR + | OC_UI_STYLE_BORDER_SIZE + | OC_UI_STYLE_ROUNDNESS); + oc_ui_panel("log", OC_UI_FLAG_DRAW_BACKGROUND | OC_UI_FLAG_DRAW_BORDER) + { + if(oc_list_empty(logLines.list)) + { + oc_ui_style_next(&(oc_ui_style){ .color = ui.theme->text2 }, + OC_UI_STYLE_COLOR); + oc_ui_label("Log"); + } + + i32 i = 0; + oc_list_for(logLines.list, logLine, oc_str8_elt, listElt) + { + char id[15]; + snprintf(id, sizeof(id), "%d", i); + oc_ui_container(id, OC_UI_FLAG_NONE) + { + oc_ui_label_str8(logLine->string); + } + i++; + } + } + } + + //----------------------------------------------------------------------------------------- + // Styling + //----------------------------------------------------------------------------------------- + // Initial values here are hardcoded from the dark theme and everything is overridden all + // the time. In a real program you'd only override what you need and supply the values from + // ui.theme or ui.theme->palette. + // + // Rule-based styling is described at + // https://www.forkingpaths.dev/posts/23-03-10/rule_based_styling_imgui.html + column("Styling", 2.0 / 3) + { + static f32 unselectedWidth = 16; + static f32 unselectedHeight = 16; + static f32 unselectedRoundness = 8; + static f32 unselectedBgR = 0.086; + static f32 unselectedBgG = 0.086; + static f32 unselectedBgB = 0.102; + static f32 unselectedBgA = 1; + static f32 unselectedBorderR = 0.976; + static f32 unselectedBorderG = 0.976; + static f32 unselectedBorderB = 0.976; + static f32 unselectedBorderA = 0.35; + static f32 unselectedBorderSize = 1; + static oc_ui_status unselectedWhenStatus = OC_UI_NONE; + + static f32 selectedWidth = 16; + static f32 selectedHeight = 16; + static f32 selectedRoundness = 8; + static f32 selectedR = 1; + static f32 selectedG = 1; + static f32 selectedB = 1; + static f32 selectedA = 1; + static f32 selectedBgR = 0.33; + static f32 selectedBgG = 0.66; + static f32 selectedBgB = 1; + static f32 selectedBgA = 1; + static oc_ui_status selectedWhenStatus = OC_UI_NONE; + + static oc_color labelFontColor = { 0.976, 0.976, 0.976, 1 }; + static oc_font* labelFont = &fontRegular; + static f32 labelFontSize = 14; + + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, + .size.height = { OC_UI_SIZE_PIXELS, 152 }, + .layout.margin.x = 320, + .layout.margin.y = 16, + .bgColor = OC_UI_DARK_THEME.bg0, + .roundness = OC_UI_DARK_THEME.roundnessSmall }, + OC_UI_STYLE_SIZE + | OC_UI_STYLE_LAYOUT_MARGINS + | OC_UI_STYLE_BG_COLOR + | OC_UI_STYLE_ROUNDNESS); + oc_ui_container("styled_radios", OC_UI_FLAG_DRAW_BACKGROUND | OC_UI_FLAG_DRAW_BORDER) + { + oc_ui_pattern unselectedPattern = { 0 }; + oc_ui_pattern_push(scratch.arena, + &unselectedPattern, + (oc_ui_selector){ .kind = OC_UI_SEL_TAG, + .tag = oc_ui_tag_make("radio") }); + oc_ui_pattern_push(scratch.arena, + &unselectedPattern, + (oc_ui_selector){ .op = OC_UI_SEL_AND, + .kind = OC_UI_SEL_STATUS, + .status = unselectedWhenStatus }); + oc_ui_style_match_after(unselectedPattern, + &(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, unselectedWidth }, + .size.height = { OC_UI_SIZE_PIXELS, unselectedHeight }, + .bgColor = { unselectedBgR, unselectedBgG, unselectedBgB, unselectedBgA }, + .borderColor = { unselectedBorderR, unselectedBorderG, unselectedBorderB, unselectedBorderA }, + .borderSize = unselectedBorderSize, + .roundness = unselectedRoundness }, + OC_UI_STYLE_SIZE + | OC_UI_STYLE_BG_COLOR + | OC_UI_STYLE_BORDER_COLOR + | OC_UI_STYLE_BORDER_SIZE + | OC_UI_STYLE_ROUNDNESS); + + oc_ui_pattern selectedPattern = { 0 }; + oc_ui_pattern_push(scratch.arena, + &selectedPattern, + (oc_ui_selector){ .kind = OC_UI_SEL_TAG, + .tag = oc_ui_tag_make("radio_selected") }); + oc_ui_pattern_push(scratch.arena, + &selectedPattern, + (oc_ui_selector){ .op = OC_UI_SEL_AND, + .kind = OC_UI_SEL_STATUS, + .status = selectedWhenStatus }); + oc_ui_style_match_after(selectedPattern, + &(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, selectedWidth }, + .size.height = { OC_UI_SIZE_PIXELS, selectedHeight }, + .color = { selectedR, selectedG, selectedB, selectedA }, + .bgColor = { selectedBgR, selectedBgG, selectedBgB, selectedBgA }, + .roundness = selectedRoundness }, + OC_UI_STYLE_SIZE + | OC_UI_STYLE_COLOR + | OC_UI_STYLE_BG_COLOR + | OC_UI_STYLE_ROUNDNESS); + + oc_ui_pattern labelPattern = { 0 }; + oc_ui_tag labelTag = oc_ui_tag_make("label"); + oc_ui_pattern_push(scratch.arena, &labelPattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = labelTag }); + oc_ui_style_match_after(labelPattern, + &(oc_ui_style){ .color = labelFontColor, + .font = *labelFont, + .fontSize = labelFontSize }, + OC_UI_STYLE_COLOR + | OC_UI_STYLE_FONT + | OC_UI_STYLE_FONT_SIZE); + + static int selectedIndex = 0; + oc_str8 options[] = { OC_STR8("I"), + OC_STR8("Am"), + OC_STR8("Stylish") }; + oc_ui_radio_group_info radioGroupInfo = { .selectedIndex = selectedIndex, + .optionCount = oc_array_size(options), + .options = options }; + oc_ui_radio_group_info result = oc_ui_radio_group("radio_group", &radioGroupInfo); + selectedIndex = result.selectedIndex; + } + + oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X, + .layout.spacing = 32 }, + OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("controls", OC_UI_FLAG_NONE) + { + oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_Y, + .layout.spacing = 16 }, + OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("unselected", OC_UI_FLAG_NONE) + { + oc_ui_style_next(&(oc_ui_style){ .fontSize = 16 }, + OC_UI_STYLE_FONT_SIZE); + oc_ui_label("Radio style"); + + oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 4 }, + OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("size", OC_UI_FLAG_NONE) + { + f32 widthSlider = (unselectedWidth - 8) / 16; + labeled_slider("Width", &widthSlider); + unselectedWidth = 8 + widthSlider * 16; + + f32 heightSlider = (unselectedHeight - 8) / 16; + labeled_slider("Height", &heightSlider); + unselectedHeight = 8 + heightSlider * 16; + + f32 roundnessSlider = (unselectedRoundness - 4) / 8; + labeled_slider("Roundness", &roundnessSlider); + unselectedRoundness = 4 + roundnessSlider * 8; + } + + oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 4 }, + OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("background", OC_UI_FLAG_NONE) + { + labeled_slider("Background R", &unselectedBgR); + labeled_slider("Background G", &unselectedBgG); + labeled_slider("Background B", &unselectedBgB); + labeled_slider("Background A", &unselectedBgA); + } + + oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 4 }, + OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("border", OC_UI_FLAG_NONE) + { + labeled_slider("Border R", &unselectedBorderR); + labeled_slider("Border G", &unselectedBorderG); + labeled_slider("Border B", &unselectedBorderB); + labeled_slider("Border A", &unselectedBorderA); + } + + f32 borderSizeSlider = unselectedBorderSize / 5; + labeled_slider("Border size", &borderSizeSlider); + unselectedBorderSize = borderSizeSlider * 5; + + oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 10 }, + OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("status_override", OC_UI_FLAG_NONE) + { + oc_ui_label("Override"); + + static int statusIndex = 0; + oc_str8 statusOptions[] = { OC_STR8("Always"), + OC_STR8("When hovering"), + OC_STR8("When dragging") }; + oc_ui_radio_group_info statusInfo = { .selectedIndex = statusIndex, + .optionCount = oc_array_size(statusOptions), + .options = statusOptions }; + oc_ui_radio_group_info result = oc_ui_radio_group("status", &statusInfo); + statusIndex = result.selectedIndex; + switch(statusIndex) + { + case 0: + unselectedWhenStatus = OC_UI_NONE; + break; + case 1: + unselectedWhenStatus = OC_UI_HOVER; + break; + case 2: + unselectedWhenStatus = OC_UI_DRAGGING; + break; + default: + break; + } + } + } + + oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_Y, + .layout.spacing = 16 }, + OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("selected", OC_UI_FLAG_NONE) + { + oc_ui_style_next(&(oc_ui_style){ .fontSize = 16 }, + OC_UI_STYLE_FONT_SIZE); + oc_ui_label("Radio selected style"); + + oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 4 }, + OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("size", OC_UI_FLAG_NONE) + { + f32 widthSlider = (selectedWidth - 8) / 16; + labeled_slider("Width", &widthSlider); + selectedWidth = 8 + widthSlider * 16; + + f32 heightSlider = (selectedHeight - 8) / 16; + labeled_slider("Height", &heightSlider); + selectedHeight = 8 + heightSlider * 16; + + f32 roundnessSlider = (selectedRoundness - 4) / 8; + labeled_slider("Roundness", &roundnessSlider); + selectedRoundness = 4 + roundnessSlider * 8; + } + + oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 4 }, + OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("color", OC_UI_FLAG_NONE) + { + labeled_slider("Center R", &selectedR); + labeled_slider("Center G", &selectedG); + labeled_slider("Center B", &selectedB); + labeled_slider("Center A", &selectedA); + } + + oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 4 }, + OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("background", OC_UI_FLAG_NONE) + { + labeled_slider("Background R", &selectedBgR); + labeled_slider("Background G", &selectedBgG); + labeled_slider("Background B", &selectedBgB); + labeled_slider("Background A", &selectedBgA); + } + + oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 10 }, + OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("status_override", OC_UI_FLAG_NONE) + { + oc_ui_style_next(&(oc_ui_style){ .size.height = { OC_UI_SIZE_PIXELS, 30 } }, + OC_UI_STYLE_SIZE_HEIGHT); + oc_ui_box_make("spacer", OC_UI_FLAG_NONE); + + oc_ui_label("Override"); + + static int statusIndex = 0; + oc_str8 statusOptions[] = { OC_STR8("Always"), + OC_STR8("When hovering"), + OC_STR8("When dragging") }; + oc_ui_radio_group_info statusInfo = { .selectedIndex = statusIndex, + .optionCount = oc_array_size(statusOptions), + .options = statusOptions }; + oc_ui_radio_group_info result = oc_ui_radio_group("status", &statusInfo); + statusIndex = result.selectedIndex; + switch(statusIndex) + { + case 0: + selectedWhenStatus = OC_UI_NONE; + break; + case 1: + selectedWhenStatus = OC_UI_HOVER; + break; + case 2: + selectedWhenStatus = OC_UI_DRAGGING; + break; + default: + break; + } + } + } + + oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_Y, + .layout.spacing = 16 }, + OC_UI_STYLE_LAYOUT_AXIS + | OC_UI_STYLE_LAYOUT_SPACING); + oc_ui_container("label", OC_UI_FLAG_NONE) + { + oc_ui_style_next(&(oc_ui_style){ .fontSize = 16 }, + OC_UI_STYLE_FONT_SIZE); + oc_ui_label("Label style"); + + 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_container("contents", 0) + oc_ui_container("font_color", OC_UI_FLAG_NONE) { - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 20 }, - .size.height = { OC_UI_SIZE_PIXELS, 200 } }, - OC_UI_STYLE_SIZE); - static f32 slider1 = 0; - oc_ui_slider("slider1", 0.2, &slider1); + oc_ui_style_match_after(oc_ui_pattern_owner(), + &(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 100 } }, + OC_UI_STYLE_SIZE_WIDTH); + oc_ui_label("Font color"); - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 20 }, - .size.height = { OC_UI_SIZE_PIXELS, 200 } }, - OC_UI_STYLE_SIZE); - static f32 slider2 = 0; - oc_ui_slider("slider2", 0.2, &slider2); - - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 20 }, - .size.height = { OC_UI_SIZE_PIXELS, 200 } }, - OC_UI_STYLE_SIZE); - static f32 slider3 = 0; - oc_ui_slider("slider3", 0.2, &slider3); + static int colorSelected = 0; + oc_str8 colorNames[] = { OC_STR8("Default"), + OC_STR8("Red"), + OC_STR8("Orange"), + OC_STR8("Amber"), + OC_STR8("Yellow"), + OC_STR8("Lime"), + OC_STR8("Light Green"), + OC_STR8("Green") }; + oc_color colors[] = { OC_UI_DARK_THEME.text0, + OC_UI_DARK_THEME.palette->red5, + OC_UI_DARK_THEME.palette->orange5, + OC_UI_DARK_THEME.palette->amber5, + OC_UI_DARK_THEME.palette->yellow5, + OC_UI_DARK_THEME.palette->lime5, + OC_UI_DARK_THEME.palette->lightGreen5, + OC_UI_DARK_THEME.palette->green5 }; + oc_ui_select_popup_info colorInfo = { .selectedIndex = colorSelected, + .optionCount = oc_array_size(colorNames), + .options = colorNames }; + oc_ui_select_popup_info colorResult = oc_ui_select_popup("color", &colorInfo); + colorSelected = colorResult.selectedIndex; + labelFontColor = colors[colorSelected]; } - } - widget_view("Horizontal Sliders") - { - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 200 }, - .size.height = { OC_UI_SIZE_PIXELS, 20 } }, - OC_UI_STYLE_SIZE); - static f32 slider1 = 0; - oc_ui_slider("slider1", 0.2, &slider1); - - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 200 }, - .size.height = { OC_UI_SIZE_PIXELS, 20 } }, - OC_UI_STYLE_SIZE); - static f32 slider2 = 0; - oc_ui_slider("slider2", 0.2, &slider2); - - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 200 }, - .size.height = { OC_UI_SIZE_PIXELS, 20 } }, - OC_UI_STYLE_SIZE); - static f32 slider3 = 0; - oc_ui_slider("slider3", 0.2, &slider3); - } - } - } - - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 0.5 }, - .size.height = { OC_UI_SIZE_PARENT, 1 } }, - OC_UI_STYLE_SIZE); - - oc_ui_container("right", debugFlags) - { - - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, - .size.height = { OC_UI_SIZE_PARENT, 0.33 } }, - OC_UI_STYLE_SIZE); - widget_view("Text box") - { - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 300 }, - .size.height = { OC_UI_SIZE_TEXT } }, - OC_UI_STYLE_SIZE); - static oc_str8 text = { 0 }; - oc_ui_text_box_result res = oc_ui_text_box("textbox", scratch.arena, text); - if(res.changed) - { - oc_arena_clear(&textArena); - text = oc_str8_push_copy(&textArena, res.text); - } - } - - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, - .size.height = { OC_UI_SIZE_PARENT, 0.33 } }, - OC_UI_STYLE_SIZE); - widget_view("Test") - { - oc_ui_pattern pattern = { 0 }; - oc_ui_pattern_push(scratch.arena, &pattern, (oc_ui_selector){ .kind = OC_UI_SEL_TEXT, .text = OC_STR8("panel") }); - oc_ui_style_match_after(pattern, &(oc_ui_style){ .bgColor = { 0.3, 0.3, 1, 1 } }, OC_UI_STYLE_BG_COLOR); - - static int selected = 0; - oc_str8 options[] = { OC_STR8("option 1"), - OC_STR8("option 2"), - OC_STR8("long option 3"), - OC_STR8("option 4"), - OC_STR8("option 5") }; - oc_ui_select_popup_info info = { .selectedIndex = selected, - .optionCount = 5, - .options = options }; - - oc_ui_select_popup_info result = oc_ui_select_popup("popup", &info); - selected = result.selectedIndex; - } - - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, - .size.height = { OC_UI_SIZE_PARENT, 0.33 } }, - OC_UI_STYLE_SIZE); - widget_view("Color") - { - oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, - .size.height = { OC_UI_SIZE_PARENT, 0.7 }, - .layout.axis = OC_UI_AXIS_X }, - OC_UI_STYLE_SIZE - | OC_UI_STYLE_LAYOUT_AXIS); - - oc_ui_panel("Panel", OC_UI_FLAG_DRAW_BORDER) - { - oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X }, - OC_UI_STYLE_LAYOUT_AXIS); - oc_ui_container("contents", 0) + 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_container("font", OC_UI_FLAG_NONE) { - oc_ui_style_next(&(oc_ui_style){ .layout.spacing = 20 }, - OC_UI_STYLE_LAYOUT_SPACING); - oc_ui_container("buttons", 0) - { - oc_ui_button("Button A"); - oc_ui_button("Button B"); - oc_ui_button("Button C"); - oc_ui_button("Button D"); - } + oc_ui_style_match_after(oc_ui_pattern_owner(), + &(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, 100 } }, + OC_UI_STYLE_SIZE_WIDTH); + oc_ui_label("Font"); - oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X, - .layout.spacing = 20 }, - OC_UI_STYLE_LAYOUT_SPACING - | OC_UI_STYLE_LAYOUT_AXIS); - - oc_ui_container("buttons2", 0) - { - oc_ui_button("Button A"); - oc_ui_button("Button B"); - oc_ui_button("Button C"); - oc_ui_button("Button D"); - } + static int fontSelected = 0; + oc_str8 fontNames[] = { OC_STR8("Regular"), + OC_STR8("Bold") }; + oc_font* fonts[] = { &fontRegular, + &fontBold }; + oc_ui_select_popup_info fontInfo = { .selectedIndex = fontSelected, + .optionCount = oc_array_size(fontNames), + .options = fontNames }; + oc_ui_select_popup_info fontResult = oc_ui_select_popup("font_style", &fontInfo); + fontSelected = fontResult.selectedIndex; + labelFont = fonts[fontSelected]; } + + f32 fontSizeSlider = (labelFontSize - 8) / 16; + labeled_slider("Font size", &fontSizeSlider); + labelFontSize = 8 + fontSizeSlider * 16; } } } } } } - if(printDebugStyle) - { - debug_print_styles(root, 0); - } + oc_canvas_select(canvas); oc_surface_select(surface); - oc_ui_draw(); - oc_render(canvas); oc_surface_present(surface); oc_scratch_end(scratch); } + return (0); +} + +int main() +{ + oc_init(); + oc_clock_init(); //TODO put that in oc_init()? + + oc_rect windowRect = { .x = 100, .y = 100, .w = frameSize.x, .h = frameSize.y }; + oc_window window = oc_window_create(windowRect, OC_STR8("test"), 0); + + oc_rect contentRect = oc_window_get_content_rect(window); + + oc_window_set_title(window, OC_STR8("Orca UI Demo")); + + surface = oc_surface_create_for_window(window, OC_CANVAS); + oc_surface_deselect(); + + oc_arena_scope scratch = oc_scratch_begin(); + + oc_font* fonts[2] = { &fontRegular, &fontBold }; + oc_str8 fontNames[2] = { + oc_path_executable_relative(scratch.arena, OC_STR8("../OpenSans-Regular.ttf")), + oc_path_executable_relative(scratch.arena, OC_STR8("../OpenSans-Bold.ttf")) + }; + + for(int i = 0; i < 2; i++) + { + oc_file file = oc_file_open(fontNames[i], OC_FILE_ACCESS_READ, 0); + if(oc_file_last_error(file) != OC_IO_OK) + { + oc_log_error("Couldn't open file %.*s\n", oc_str8_ip(fontNames[i])); + } + u64 size = oc_file_size(file); + char* buffer = (char*)oc_arena_push(scratch.arena, size); + oc_file_read(file, size, buffer); + oc_file_close(file); + oc_unicode_range ranges[5] = { OC_UNICODE_BASIC_LATIN, + OC_UNICODE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT, + OC_UNICODE_LATIN_EXTENDED_A, + OC_UNICODE_LATIN_EXTENDED_B, + OC_UNICODE_SPECIALS }; + + *fonts[i] = oc_font_create_from_memory(oc_str8_from_buffer(size, buffer), 5, ranges); + } + oc_scratch_end(scratch); + + oc_arena_init(&textArena); + oc_arena_init(&logArena); + oc_list_init(&logLines.list); + + // start app + oc_window_bring_to_front(window); + oc_window_focus(window); + + oc_thread* runloopThread = oc_thread_create(ui_runloop, 0); + + while(!oc_should_quit()) + { + oc_pump_events(-1); + //TODO: what to do with mem scratch here? + } oc_surface_destroy(surface); oc_terminate(); diff --git a/src/libc-shim/include/string.h b/src/libc-shim/include/string.h index 5204d64..ae0161f 100644 --- a/src/libc-shim/include/string.h +++ b/src/libc-shim/include/string.h @@ -10,4 +10,5 @@ int strcmp(const char* s1, const char* s2); int strncmp(const char* s1, const char* s2, size_t n); char* strcpy(char* __restrict s1, const char* __restrict s2); +#define snprintf stbsp_snprintf #define vsnprintf stbsp_vsnprintf diff --git a/src/ui/ui.c b/src/ui/ui.c index b99c789..c526f40 100644 --- a/src/ui/ui.c +++ b/src/ui/ui.c @@ -1021,23 +1021,54 @@ bool oc_ui_layout_downward_dependency(oc_ui_box* child, int axis) void oc_ui_layout_downward_dependent_size(oc_ui_context* ui, oc_ui_box* box, int axis) { - //NOTE: layout children and compute spacing - f32 count = 0; + //NOTE: layout children and compute spacing and minimum size + i32 count = 0; + f32 minSum = 0; + oc_list_for(box->children, child, oc_ui_box, listElt) { if(!oc_ui_box_hidden(child)) { oc_ui_layout_downward_dependent_size(ui, child, axis); - if(box->style.layout.axis == axis - && !child->style.floating.c[axis]) + if(!child->style.floating.c[axis]) { - count++; + if(box->style.layout.axis == axis) + { + count++; + minSum += child->minSize[axis]; + } + else + { + minSum = oc_max(minSum, child->minSize[axis]); + } } } } box->spacing[axis] = oc_max(0, count - 1) * box->style.layout.spacing; + switch(box->style.size.c[axis].kind) + { + case OC_UI_SIZE_TEXT: + case OC_UI_SIZE_PIXELS: + box->minSize[axis] = box->rect.c[2 + axis]; + break; + + case OC_UI_SIZE_CHILDREN: + case OC_UI_SIZE_PARENT: + case OC_UI_SIZE_PARENT_MINUS_PIXELS: + { + int overflowFlag = (OC_UI_FLAG_OVERFLOW_ALLOW_X << axis); + + if(!(box->flags & overflowFlag)) + { + box->minSize[axis] = minSum + box->spacing[axis] + 2 * box->style.layout.margin.c[axis]; + } + } + break; + } + box->minSize[axis] = oc_max(box->minSize[axis], box->style.size.c[axis].minSize); + oc_ui_size* size = &box->style.size.c[axis]; if(size->kind == OC_UI_SIZE_CHILDREN) { @@ -1082,77 +1113,209 @@ void oc_ui_layout_upward_dependent_size(oc_ui_context* ui, oc_ui_box* box, int a oc_ui_size* size = &child->style.size.c[axis]; if(size->kind == OC_UI_SIZE_PARENT) { - child->rect.c[2 + axis] = availableSize * size->value; + child->rect.c[2 + axis] = oc_max(child->minSize[axis], availableSize * size->value); } else if(size->kind == OC_UI_SIZE_PARENT_MINUS_PIXELS) { - child->rect.c[2 + axis] = oc_max(0, availableSize - size->value); + child->rect.c[2 + axis] = oc_max(child->minSize[axis], oc_max(0, availableSize - size->value)); } } - //NOTE: solve downard conflicts - int overflowFlag = (OC_UI_FLAG_ALLOW_OVERFLOW_X << axis); - f32 sum = 0; + //NOTE: solve downward conflicts + int overflowFlag = (OC_UI_FLAG_OVERFLOW_ALLOW_X << axis); - if(box->style.layout.axis == axis) + if(!(box->flags & overflowFlag)) { - //NOTE: if we're solving in the layout axis, first compute total sum of children and - // total slack available - f32 slack = 0; - - oc_list_for(box->children, child, oc_ui_box, listElt) + if(box->style.layout.axis == axis) { - if(!oc_ui_box_hidden(child) - && !child->style.floating.c[axis]) - { - sum += child->rect.c[2 + axis]; - slack += child->rect.c[2 + axis] * child->style.size.c[axis].relax; - } - } + f32 prevSum = FLT_MAX; + int count = 0; - if(!(box->flags & overflowFlag)) - { - //NOTE: then remove excess proportionally to each box slack, and recompute children sum. - f32 totalContents = sum + box->spacing[axis] + 2 * box->style.layout.margin.c[axis]; - f32 excess = oc_clamp_low(totalContents - box->rect.c[2 + axis], 0); - f32 alpha = oc_clamp(excess / slack, 0, 1); - - sum = 0; + //NOTE: take into account the _original size_ minus the _original slack_ in minimum size. This way the widget + // never gives up more than wantedSize * relax. oc_list_for(box->children, child, oc_ui_box, listElt) { - f32 relax = child->style.size.c[axis].relax; - child->rect.c[2 + axis] -= alpha * child->rect.c[2 + axis] * relax; - sum += child->rect.c[2 + axis]; + if(!oc_ui_box_hidden(child) + && !child->style.floating.c[axis]) + { + child->minSize[axis] = oc_max(child->minSize[axis], child->rect.c[2 + axis] * (1 - child->style.size.c[axis].relax)); + } + } + + //NOTE: Loop while we can remove excess. Each iterations reaffects excess to boxes that still have some slack. + while(1) + { + //NOTE: if we're solving in the layout axis, first compute total sum of children and + // total slack available + f32 sum = 0; + f32 slack = 0; + + oc_list_for(box->children, child, oc_ui_box, listElt) + { + if(!oc_ui_box_hidden(child) + && !child->style.floating.c[axis]) + { + sum += child->rect.c[2 + axis]; + slack += oc_min(child->rect.c[2 + axis] * child->style.size.c[axis].relax, + child->rect.c[2 + axis] - child->minSize[axis]); + } + } + if(prevSum - sum < 1) + { + break; + } + count++; + prevSum = sum; + + //NOTE: then remove excess proportionally to each box slack + f32 totalContents = sum + box->spacing[axis] + 2 * box->style.layout.margin.c[axis]; + f32 excess = oc_clamp_low(totalContents - box->rect.c[2 + axis], 0); + f32 alpha = oc_clamp(excess / slack, 0, 1); + + oc_list_for(box->children, child, oc_ui_box, listElt) + { + if(!oc_ui_box_hidden(child) && !child->style.floating.c[axis]) + { + f32 relax = child->style.size.c[axis].relax; + f32 minSize = child->minSize[axis]; + f32 remove = alpha * oc_clamp(child->rect.c[2 + axis] * relax, 0, child->rect.c[2 + axis] - child->minSize[axis]); + + child->rect.c[2 + axis] = oc_max(minSize, child->rect.c[2 + axis] - remove); + } + } } } - } - else - { - //NOTE: if we're solving on the secondary axis, we remove excess to each box individually - // according to its own slack. Children sum is the maximum child size. - - oc_list_for(box->children, child, oc_ui_box, listElt) + else { - if(!oc_ui_box_hidden(child) && !child->style.floating.c[axis]) + //NOTE: if we're solving on the secondary axis, we remove excess to each box individually + // according to its own slack. + oc_list_for(box->children, child, oc_ui_box, listElt) { - if(!(box->flags & overflowFlag)) + if(!oc_ui_box_hidden(child) && !child->style.floating.c[axis]) { f32 totalContents = child->rect.c[2 + axis] + 2 * box->style.layout.margin.c[axis]; f32 excess = oc_clamp_low(totalContents - box->rect.c[2 + axis], 0); f32 relax = child->style.size.c[axis].relax; - child->rect.c[2 + axis] -= oc_min(excess, child->rect.c[2 + axis] * relax); + f32 minSize = child->minSize[axis]; + f32 remove = oc_min(excess, child->rect.c[2 + axis] * relax); + + child->rect.c[2 + axis] = oc_max(minSize, child->rect.c[2 + axis] - remove); } + } + } + } + f32 sum = 0; + + //NOTE: recurse in children and recompute children sum + oc_list_for(box->children, child, oc_ui_box, listElt) + { + oc_ui_layout_upward_dependent_size(ui, child, axis); + + if(!oc_ui_box_hidden(child) + && !child->style.floating.c[axis]) + { + if(box->style.layout.axis == axis) + { + sum += child->rect.c[2 + axis]; + } + else + { sum = oc_max(sum, child->rect.c[2 + axis]); } } } - box->childrenSum[axis] = sum; - //NOTE: recurse in children + OC_ASSERT(box->rect.c[2 + axis] >= box->minSize[axis], "parent->string = %.*s, box->string = %.*s, axis = %i, box->size[axis].kind = %i, box->rect.c[2+axis] = %f, box->minSize[axis] = %f", + oc_str8_ip(box->parent->string), + oc_str8_ip(box->string), + axis, + box->style.size.c[axis].kind, + box->rect.c[2 + axis], + box->minSize[axis]); + /* + if(!(box->flags & overflowFlag) && !oc_list_empty(box->children)) + { + f32 minSize = sum + 2 * box->style.layout.margin.c[axis] + box->spacing[axis]; + box->rect.c[2 + axis] = oc_max(minSize, box->rect.c[2 + axis]); + } + */ +} + +void oc_ui_layout_upward_dependent_fixup(oc_ui_context* ui, oc_ui_box* box, int axis) +{ + f32 margin = box->style.layout.margin.c[axis]; + f32 availableSize = oc_max(0, box->rect.c[2 + axis] - box->spacing[axis] - 2 * margin); + + if(axis == box->style.layout.axis) + { + f32 availableToParentSized = availableSize; + f32 relaxSum = 0; + + oc_list_for(box->children, child, oc_ui_box, listElt) + { + oc_ui_size* size = &child->style.size.c[axis]; + if(size->kind == OC_UI_SIZE_PARENT || size->kind == OC_UI_SIZE_PARENT_MINUS_PIXELS) + { + f32 wantedSize = 0; + if(size->kind == OC_UI_SIZE_PARENT) + { + wantedSize = availableSize * size->value; + } + else + { + wantedSize = availableSize - size->value; + } + if(wantedSize > child->rect.c[2 + axis]) + { + relaxSum += size->relax; + } + } + availableToParentSized -= child->rect.c[2 + axis]; + } + availableToParentSized = oc_max(0, availableToParentSized); + + if(availableToParentSized && relaxSum) + { + oc_list_for(box->children, child, oc_ui_box, listElt) + { + oc_ui_size* size = &child->style.size.c[axis]; + if(size->kind == OC_UI_SIZE_PARENT || size->kind == OC_UI_SIZE_PARENT_MINUS_PIXELS) + { + f32 wantedSize = 0; + if(size->kind == OC_UI_SIZE_PARENT) + { + wantedSize = availableSize * size->value; + } + else + { + wantedSize = availableSize - size->value; + } + + if(wantedSize > child->rect.c[2 + axis]) + { + child->rect.c[2 + axis] += availableToParentSized * (size->relax / relaxSum); + } + } + } + } + } + oc_list_for(box->children, child, oc_ui_box, listElt) { - oc_ui_layout_upward_dependent_size(ui, child, axis); + if(axis != box->style.layout.axis) + { + oc_ui_size* size = &child->style.size.c[axis]; + if(size->kind == OC_UI_SIZE_PARENT) + { + child->rect.c[2 + axis] = oc_max(child->rect.c[2 + axis], availableSize * size->value); + } + else if(size->kind == OC_UI_SIZE_PARENT_MINUS_PIXELS) + { + child->rect.c[2 + axis] = oc_max(child->rect.c[2 + axis], oc_max(0, availableSize - size->value)); + } + } + oc_ui_layout_upward_dependent_fixup(ui, child, axis); } } @@ -1233,6 +1396,11 @@ void oc_ui_layout_compute_rect(oc_ui_context* ui, oc_ui_box* box, oc_vec2 pos) currentPos.c[layoutAxis] += child->rect.c[2 + layoutAxis] + spacing; } } + if(isnan(box->rect.w) || isnan(box->rect.h)) + { + oc_log_error("error in box %.*s\n", oc_str8_ip(box->string)); + OC_ASSERT(0); + } } void oc_ui_layout_find_next_hovered_recursive(oc_ui_context* ui, oc_ui_box* box, oc_vec2 p) @@ -1285,6 +1453,8 @@ void oc_ui_solve_layout(oc_ui_context* ui) { oc_ui_layout_downward_dependent_size(ui, ui->root, axis); oc_ui_layout_upward_dependent_size(ui, ui->root, axis); + + // oc_ui_layout_upward_dependent_fixup(ui, ui->root, axis); } oc_ui_layout_compute_rect(ui, ui->root, (oc_vec2){ 0, 0 }); @@ -1428,6 +1598,15 @@ void oc_ui_draw_box(oc_ui_box* box) oc_set_color(style->borderColor); oc_ui_rectangle_stroke(box->rect, style->roundness); } + +#if 0 + if(box->rect.w && box->rect.h) + { + oc_set_width(1); + oc_set_color_rgba(1, 0, 0, 1); + oc_ui_rectangle_stroke(box->rect, 0); + } +#endif } void oc_ui_draw() @@ -1619,7 +1798,7 @@ oc_ui_sig oc_ui_button_str8(oc_str8 label) .layout.margin.y = 6, .color = theme->primary, .bgColor = theme->fill0, - .roundness = 3 }; + .roundness = theme->roundnessSmall }; oc_ui_style_mask defaultMask = OC_UI_STYLE_SIZE_WIDTH | OC_UI_STYLE_SIZE_HEIGHT @@ -1706,7 +1885,7 @@ oc_ui_sig oc_ui_checkbox(const char* name, bool* checked) .size.height = { OC_UI_SIZE_PIXELS, 16 }, .bgColor = theme->primary, .color = theme->white, - .roundness = 3 }; + .roundness = theme->roundnessSmall }; oc_ui_style_mask defaultMask = OC_UI_STYLE_SIZE_WIDTH | OC_UI_STYLE_SIZE_HEIGHT @@ -1751,7 +1930,7 @@ oc_ui_sig oc_ui_checkbox(const char* name, bool* checked) .bgColor = { 0 }, .borderColor = theme->text3, .borderSize = 1, - .roundness = 3 }; + .roundness = theme->roundnessSmall }; oc_ui_style_mask defaultMask = OC_UI_STYLE_SIZE_WIDTH | OC_UI_STYLE_SIZE_HEIGHT @@ -2121,13 +2300,23 @@ void oc_ui_panel_begin(const char* str, oc_ui_flags flags) flags = flags | OC_UI_FLAG_CLIP | OC_UI_FLAG_BLOCK_MOUSE - | OC_UI_FLAG_ALLOW_OVERFLOW_X - | OC_UI_FLAG_ALLOW_OVERFLOW_Y + | OC_UI_FLAG_OVERFLOW_ALLOW_X + | OC_UI_FLAG_OVERFLOW_ALLOW_Y | OC_UI_FLAG_SCROLL_WHEEL_X | OC_UI_FLAG_SCROLL_WHEEL_Y; + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1, 1 }, + .size.height = { OC_UI_SIZE_PARENT, 1, 1 }, + .layout.margin.x = 0, + .layout.margin.y = 0 }, + OC_UI_STYLE_SIZE + | OC_UI_STYLE_LAYOUT_MARGINS); oc_ui_box_begin(str, flags); + oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, + .size.height = { OC_UI_SIZE_PARENT, 1 } }, + OC_UI_STYLE_SIZE); + oc_ui_box_begin("contents", 0); } @@ -2260,7 +2449,7 @@ void oc_ui_tooltip(const char* label) .floatTarget = { 24, 0 }, .bgColor = theme->palette->grey7, .color = theme->bg0, - .roundness = 6 }; + .roundness = theme->roundnessMedium }; oc_ui_style_mask contentsMask = OC_UI_STYLE_SIZE | OC_UI_STYLE_LAYOUT_MARGINS | OC_UI_STYLE_FLOAT @@ -2532,7 +2721,7 @@ oc_ui_select_popup_info oc_ui_select_popup(const char* name, oc_ui_select_popup_ OC_UI_FLAG_CLICKABLE | OC_UI_FLAG_DRAW_BACKGROUND | OC_UI_FLAG_DRAW_BORDER - | OC_UI_FLAG_ALLOW_OVERFLOW_X + | OC_UI_FLAG_OVERFLOW_ALLOW_X | OC_UI_FLAG_CLIP); f32 maxOptionWidth = 0; @@ -2552,7 +2741,7 @@ oc_ui_select_popup_info oc_ui_select_popup(const char* name, oc_ui_select_popup_ .layout.margin.x = 12, .layout.margin.y = 6, .bgColor = theme->fill0, - .roundness = 3 }, + .roundness = theme->roundnessSmall }, OC_UI_STYLE_SIZE | OC_UI_STYLE_LAYOUT_MARGIN_X | OC_UI_STYLE_LAYOUT_MARGIN_Y @@ -2563,7 +2752,16 @@ oc_ui_select_popup_info oc_ui_select_popup(const char* name, oc_ui_select_popup_ { oc_ui_container("selected_option", 0) { - oc_ui_label_str8(info->options[info->selectedIndex]); + if(info->selectedIndex == -1) + { + oc_ui_style_next(&(oc_ui_style){ .color = theme->text2 }, + OC_UI_STYLE_COLOR); + oc_ui_label_str8(info->placeholder); + } + else + { + oc_ui_label_str8(info->options[info->selectedIndex]); + } } oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PIXELS, button->rect.h }, @@ -2609,7 +2807,7 @@ oc_ui_select_popup_info oc_ui_select_popup(const char* name, oc_ui_select_popup_ .bgColor = theme->bg3, .borderColor = theme->elevatedBorder, .borderSize = 1, - .roundness = 6 }, + .roundness = theme->roundnessMedium }, OC_UI_STYLE_SIZE | OC_UI_STYLE_FLOAT | OC_UI_STYLE_LAYOUT @@ -2744,9 +2942,11 @@ oc_ui_radio_group_info oc_ui_radio_group(const char* name, oc_ui_radio_group_inf for(int i = 0; i < info->optionCount; i++) { oc_ui_style_next(&(oc_ui_style){ .layout.axis = OC_UI_AXIS_X, - .layout.spacing = 8 }, + .layout.spacing = 8, + .layout.align.y = OC_UI_ALIGN_CENTER }, OC_UI_STYLE_LAYOUT_AXIS - | OC_UI_STYLE_LAYOUT_SPACING); + | OC_UI_STYLE_LAYOUT_SPACING + | OC_UI_STYLE_LAYOUT_ALIGN_Y); oc_ui_box* row = oc_ui_box_begin_str8(info->options[i], OC_UI_FLAG_CLICKABLE); oc_ui_flags flags = OC_UI_FLAG_DRAW_BACKGROUND | OC_UI_FLAG_DRAW_BORDER | OC_UI_FLAG_DRAW_PROC; oc_ui_box* radio = oc_ui_box_make("radio", flags); @@ -2825,6 +3025,7 @@ oc_ui_radio_group_info oc_ui_radio_group(const char* name, oc_ui_radio_group_inf oc_ui_container("label", 0) { + oc_ui_tag_next("label"); oc_ui_label_str8(info->options[i]); } @@ -3626,7 +3827,7 @@ oc_ui_text_box_result oc_ui_text_box(const char* name, oc_arena* arena, oc_str8 oc_ui_style frameStyle = { .layout.margin.x = 12, .layout.margin.y = 6, .bgColor = theme->fill0, - .roundness = 3 }; + .roundness = theme->roundnessSmall }; oc_ui_style_mask frameMask = OC_UI_STYLE_LAYOUT_MARGIN_X | OC_UI_STYLE_LAYOUT_MARGIN_Y | OC_UI_STYLE_BG_COLOR @@ -4110,6 +4311,7 @@ oc_ui_theme OC_UI_DARK_THEME = { .primary = { 0.33, 0.66, 1, 1 }, // blue5 .primaryHover = { 0.5, 0.757, 1, 1 }, // blue6 .primaryActive = { 0.66, 0.84, 1, 1 }, // blue7 + .border = { 1, 1, 1, 0.08 }, .fill0 = { 1, 1, 1, 0.12 }, .fill1 = { 1, 1, 1, 0.16 }, .fill2 = { 1, 1, 1, 0.2 }, @@ -4123,7 +4325,11 @@ oc_ui_theme OC_UI_DARK_THEME = { .text2 = { 0.976, 0.976, 0.976, 0.6 }, // grey9 .text3 = { 0.976, 0.976, 0.976, 0.35 }, // grey9 .sliderThumbBorder = { 0, 0, 0, 0.075 }, - .elevatedBorder = { 1, 1, 1, 0.1 } + .elevatedBorder = { 1, 1, 1, 0.1 }, + + .roundnessSmall = 3, + .roundnessMedium = 6, + .roundnessLarge = 9 }; oc_ui_palette OC_UI_LIGHT_PALETTE = { @@ -4296,6 +4502,7 @@ oc_ui_theme OC_UI_LIGHT_THEME = { .primary = { 0.000, 0.392, 0.980, 1 }, // blue5 .primaryHover = { 0.000, 0.384, 0.839, 1 }, // blue6 .primaryActive = { 0.000, 0.310, 0.702, 1 }, // blue7 + .border = { 0.110, 0.122, 0.137, 0.08 }, // grey9 .fill0 = { 0.180, 0.196, 0.220, 0.05 }, // grey8 .fill1 = { 0.180, 0.196, 0.220, 0.09 }, // grey8 .fill2 = { 0.180, 0.196, 0.220, 0.13 }, // grey8 @@ -4308,6 +4515,10 @@ oc_ui_theme OC_UI_LIGHT_THEME = { .text1 = { 0.110, 0.122, 0.137, 0.8 }, // grey9 .text2 = { 0.110, 0.122, 0.137, 0.62 }, // grey9 .text3 = { 0.110, 0.122, 0.137, 0.35 }, // grey9 - .sliderThumbBorder = { 0, 0, 0, 0.075 }, - .elevatedBorder = { 0, 0, 0, 0.075 } + .sliderThumbBorder = { 0, 0, 0, 0.125 }, + .elevatedBorder = { 0, 0, 0, 0.075 }, + + .roundnessSmall = 3, + .roundnessMedium = 6, + .roundnessLarge = 9 }; diff --git a/src/ui/ui.h b/src/ui/ui.h index 9f86018..e411d4b 100644 --- a/src/ui/ui.h +++ b/src/ui/ui.h @@ -69,7 +69,7 @@ typedef struct oc_ui_layout typedef enum oc_ui_size_kind { - OC_UI_SIZE_TEXT, + OC_UI_SIZE_TEXT = 0, OC_UI_SIZE_PIXELS, OC_UI_SIZE_CHILDREN, OC_UI_SIZE_PARENT, @@ -82,6 +82,7 @@ typedef struct oc_ui_size oc_ui_size_kind kind; f32 value; f32 relax; + f32 minSize; } oc_ui_size; typedef union oc_ui_box_size @@ -349,6 +350,7 @@ typedef struct oc_ui_theme oc_color primary; oc_color primaryHover; oc_color primaryActive; + oc_color border; oc_color fill0; oc_color fill1; oc_color fill2; @@ -364,6 +366,10 @@ typedef struct oc_ui_theme oc_color sliderThumbBorder; oc_color elevatedBorder; + f32 roundnessSmall; + f32 roundnessMedium; + f32 roundnessLarge; + oc_ui_palette* palette; } oc_ui_theme; @@ -474,8 +480,8 @@ typedef enum OC_UI_FLAG_ACTIVE_ANIMATION = (1 << 5), //WARN: these two following flags need to be kept as consecutive bits to // play well with axis-agnostic functions - OC_UI_FLAG_ALLOW_OVERFLOW_X = (1 << 6), - OC_UI_FLAG_ALLOW_OVERFLOW_Y = (1 << 7), + OC_UI_FLAG_OVERFLOW_ALLOW_X = (1 << 6), + OC_UI_FLAG_OVERFLOW_ALLOW_Y = (1 << 7), OC_UI_FLAG_CLIP = (1 << 8), OC_UI_FLAG_DRAW_BACKGROUND = (1 << 9), OC_UI_FLAG_DRAW_FOREGROUND = (1 << 10), @@ -483,7 +489,7 @@ typedef enum OC_UI_FLAG_DRAW_TEXT = (1 << 12), OC_UI_FLAG_DRAW_PROC = (1 << 13), - OC_UI_FLAG_OVERLAY = (1 << 14), + OC_UI_FLAG_OVERLAY = (1 << 16), } oc_ui_flags; struct oc_ui_box @@ -520,6 +526,7 @@ struct oc_ui_box oc_vec2 floatPos; f32 childrenSum[2]; f32 spacing[2]; + f32 minSize[2]; oc_rect rect; // signals @@ -651,12 +658,8 @@ ORCA_API void oc_ui_set_theme(oc_ui_theme* theme); ORCA_API oc_ui_key oc_ui_key_make_str8(oc_str8 string); ORCA_API oc_ui_key oc_ui_key_make_path(oc_str8_list path); -ORCA_API oc_ui_box* oc_ui_box_lookup_key(oc_ui_key key); -ORCA_API oc_ui_box* oc_ui_box_lookup_str8(oc_str8 string); - // C-string helper #define oc_ui_key_make(s) oc_ui_key_make_str8(OC_STR8(s)) -#define oc_ui_box_lookup(s) oc_ui_box_lookup_str8(OC_STR8(s)) //------------------------------------------------------------------------------------- // Box hierarchy building @@ -672,6 +675,9 @@ ORCA_API void oc_ui_box_push(oc_ui_box* box); ORCA_API void oc_ui_box_pop(void); ORCA_API oc_ui_box* oc_ui_box_top(void); +ORCA_API oc_ui_box* oc_ui_box_lookup_key(oc_ui_key key); +ORCA_API oc_ui_box* oc_ui_box_lookup_str8(oc_str8 string); + ORCA_API void oc_ui_box_set_draw_proc(oc_ui_box* box, oc_ui_box_draw_proc proc, void* data); // C-string helpers @@ -772,9 +778,10 @@ ORCA_API oc_ui_text_box_result oc_ui_text_box(const char* name, oc_arena* arena, typedef struct oc_ui_select_popup_info { bool changed; - int selectedIndex; + int selectedIndex; // -1 if nothing is selected int optionCount; oc_str8* options; + oc_str8 placeholder; } 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);