orca/sketches/ui/main.c

1003 lines
50 KiB
C

/*************************************************************************
*
* Orca
* Copyright 2023 Martin Fouilleul and the Orca project contributors
* See LICENSE.txt for licensing information
*
**************************************************************************/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define _USE_MATH_DEFINES //NOTE: necessary for MSVC
#include <math.h>
#include "orca.h"
/*
void debug_print_indent(int indent)
{
for(int i = 0; i < indent; i++)
{
oc_log_info(" ");
}
}
void debug_print_rule(oc_ui_style_rule* rule)
{
oc_list_for(&rule->pattern.l, selector, oc_ui_selector, listElt)
{
switch(selector->kind)
{
case OC_UI_SEL_ANY:
oc_log_info("any: ");
break;
case OC_UI_SEL_OWNER:
oc_log_info("owner: ");
break;
case OC_UI_SEL_TEXT:
oc_log_info("text='%.*s': ", (int)selector->text.len, selector->text.ptr);
break;
case OC_UI_SEL_TAG:
oc_log_info("tag=0x%llx: ", selector->tag.hash);
break;
case OC_UI_SEL_STATUS:
{
if(selector->status & OC_UI_HOVER)
{
oc_log_info("hover: ");
}
if(selector->status & OC_UI_ACTIVE)
{
oc_log_info("active: ");
}
if(selector->status & OC_UI_DRAGGING)
{
oc_log_info("dragging: ");
}
}
break;
case OC_UI_SEL_KEY:
oc_log_info("key=0x%llx: ", selector->key.hash);
break;
default:
oc_log_info("unknown: ");
break;
}
}
oc_log_info("=> font size = %f\n", rule->style->fontSize);
}
void debug_print_size(oc_ui_box* box, oc_ui_axis axis, int indent)
{
debug_print_indent(indent);
oc_log_info("size %s: ", axis == OC_UI_AXIS_X ? "x" : "y");
f32 value = box->targetStyle->size.c[axis].value;
switch(box->targetStyle->size.c[axis].kind)
{
case OC_UI_SIZE_TEXT:
oc_log_info("text\n");
break;
case OC_UI_SIZE_CHILDREN:
oc_log_info("children\n");
break;
case OC_UI_SIZE_PARENT:
oc_log_info("parent: %f\n", value);
break;
case OC_UI_SIZE_PARENT_MINUS_PIXELS:
oc_log_info("parent minus pixels: %f\n", value);
break;
case OC_UI_SIZE_PIXELS:
oc_log_info("pixels: %f\n", value);
break;
}
}
void debug_print_styles(oc_ui_box* box, int indent)
{
debug_print_indent(indent);
oc_log_info("### box '%.*s'\n", (int)box->string.len, box->string.ptr);
indent++;
debug_print_indent(indent);
oc_log_info("font size: %f\n", box->targetStyle->fontSize);
debug_print_size(box, OC_UI_AXIS_X, indent);
debug_print_size(box, OC_UI_AXIS_Y, indent);
if(!oc_list_empty(&box->beforeRules))
{
debug_print_indent(indent);
oc_log_info("before rules:\n");
oc_list_for(&box->beforeRules, rule, oc_ui_style_rule, boxElt)
{
debug_print_indent(indent + 1);
debug_print_rule(rule);
}
}
if(!oc_list_empty(&box->afterRules))
{
debug_print_indent(indent);
oc_log_info("after rules:\n");
oc_list_for(&box->afterRules, rule, oc_ui_style_rule, boxElt)
{
debug_print_indent(indent + 1);
debug_print_rule(rule);
}
}
if(!oc_list_empty(&box->children))
{
debug_print_indent(indent);
oc_log_info("children:\n");
indent++;
oc_list_for(&box->children, child, oc_ui_box, listElt)
{
debug_print_styles(child, indent);
}
}
}
*/
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
{
CMD_NONE,
CMD_SET_DARK_THEME,
CMD_SET_LIGHT_THEME
} cmd;
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_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 | OC_UI_FLAG_DRAW_BORDER);
}
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);
}
}
i32 ui_runloop(void* user)
{
canvas = oc_canvas_create();
oc_ui_init(&ui);
while(!oc_should_quit())
{
oc_arena_scope scratch = oc_scratch_begin();
oc_event* event = 0;
while((event = oc_next_event(scratch.arena)) != 0)
{
oc_ui_process_event(event);
switch(event->type)
{
case OC_EVENT_WINDOW_CLOSE:
{
oc_request_quit();
}
break;
case OC_EVENT_WINDOW_RESIZE:
{
frameSize = (oc_vec2){ event->move.content.w, event->move.content.h };
}
break;
default:
break;
}
}
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_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_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_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_AXIS
| OC_UI_STYLE_LAYOUT_MARGINS
| OC_UI_STYLE_LAYOUT_SPACING);
oc_ui_container("background", OC_UI_FLAG_DRAW_BACKGROUND)
{
column("Widgets", 1.0 / 3)
{
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)
{
//-----------------------------------------------------------------------------
// Label
//-----------------------------------------------------------------------------
oc_ui_label("Label");
//-----------------------------------------------------------------------------
// Button
//-----------------------------------------------------------------------------
if(oc_ui_button("Button").clicked)
{
log_push("Button 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)
{
//-------------------------------------------------------------------------
// 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("font_color", 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("Font color");
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];
}
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_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");
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;
}
}
}
}
}
}
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();
return (0);
}