per_thread UI_State* ui_state; global F32 ui_g_dt; //////////////////////////////// //~ "Generated"/meta functions #include "ui_meta.c" //////////////////////////////// //~ Basic type functions //- Boxes root_function B32 UI_box_is_nil(UI_Box *box) { return box == 0 || box == &ui_g_nil_box; } root_function UI_BoxRec UI_box_recurse_depth_first(UI_Box *box, UI_Box *stopper, MemberOffset sib, MemberOffset child) { UI_BoxRec rec = {0}; rec.next = &ui_g_nil_box; // We check what we get from the child offset. // If it is not nil we set the next pointer to that box. if(!UI_box_is_nil(MemberFromOff(box, UI_Box *, child))) { rec.next = MemberFromOff(box, UI_Box *, child); rec.push_count = 1; } else { // If the child is nil, we loop over all boxes going up the parent chain, // until we hit stopper. // As soon as we hit a sibling that is non-nil, we put that in next and return. for(UI_Box *b = box; !UI_box_is_nil(b) && b != stopper; b = b->parent) { if(!UI_box_is_nil(MemberFromOff(b, UI_Box *, sib))) { rec.next = MemberFromOff(b, UI_Box *, sib); break; } rec.pop_count += 1; } } return rec; } //- sizes root_function UI_Size UI_size_make(UI_SizeKind kind, F32 value, F32 strictness) { UI_Size result = {0}; result.kind = kind; result.value = value; result.strictness = strictness; return result; } //- ID strings root_function String8 UI_hash_part_from_box_string(String8 string) { // TODO(anton): Implement ryans stuff here with substrings return string; } //- Keys root_function UI_Key UI_key_zero(void) { UI_Key key = {0}; return key; } root_function UI_Key UI_key_from_string(UI_Key seed, String8 string) { UI_Key key = {0}; if(string.size > 0) { MemoryCopyStruct(&key, &seed); for(U64 i = 0; i < string.size; i += 1) { key.u64[0] = ((key.u64[0] << 5) + key.u64[0]) + string.str[i]; } } return key; } root_function B32 UI_key_match(UI_Key a, UI_Key b) { return a.u64[0] == b.u64[0]; } root_function UI_Signal UI_signal_from_box(UI_Box *box) { UI_Signal sig = {box}; // TODO(anton): possibly clipped box rect Rng2_F32 rect = box->rect; B32 ctx_menu_is_ancestor = 0; { for(UI_Box *parent = box; !UI_box_is_nil(parent); parent = parent->parent) { if(parent == ui_state->ctx_menu_root) { ctx_menu_is_ancestor = 1; break; } } } for(OS_Event *event = ui_state->events->first, *next = 0; event !=0; event = next) { B32 taken = 0; // flag for consume next = event->next; //- unpack event Vec2_F32 event_mouse = event->position; B32 event_mouse_in_bounds = rng2_contains_vec2_F32(rect, event_mouse); UI_MouseButtonKind event_mouse_button_kind = (event->key == OS_Key_MouseLeft ? UI_MouseButtonKind_Left : event->key == OS_Key_MouseRight ? UI_MouseButtonKind_Right : event->key == OS_Key_MouseMiddle ? UI_MouseButtonKind_Middle : UI_MouseButtonKind_Left); B32 event_key_is_mouse = (event->key == OS_Key_MouseLeft || event->key == OS_Key_MouseRight || event->key == OS_Key_MouseMiddle); //- Mouse presses if(box->flags & UI_BoxFlag_MouseClickable && event->kind == OS_EventKind_Press && event_mouse_in_bounds && event_key_is_mouse) { ui_state->hot_box_key = box->key; ui_state->active_box_key[event_mouse_button_kind] = box->key; sig.flag |= (UI_SignalFlag_LeftPressed<events, event); } } ////////////////////////////// //- mouse is over this box's rect -> always mark mouse-over // But the rect may be non-visible Vec2_F32 mouse_pos = ui_state->mouse; B32 rect_contains_mouse = rng2_contains_vec2_F32(rect, mouse_pos); if(rect_contains_mouse) { sig.flag |= UI_SignalFlag_MouseOver; } ////////////////////////////// //- mouse is over this box's rect, no other hot key? -> set hot key, mark hovering // if(box->flags & UI_BoxFlag_MouseClickable && rect_contains_mouse) // && //(UI_key_match(ui_state->hot_box_key, UI_key_zero()) || UI_key_match(ui_state->hot_box_key, box->key)) //&& //(UI_key_match(ui_state->active_box_key[UI_MouseButtonKind_Left], UI_key_zero()) || UI_key_match(ui_state->active_box_key[UI_MouseButtonKind_Left], box->key)) && //(UI_key_match(ui_state->active_box_key[UI_MouseButtonKind_Middle], UI_key_zero()) || UI_key_match(ui_state->active_box_key[UI_MouseButtonKind_Middle], box->key)) && //(UI_key_match(ui_state->active_box_key[UI_MouseButtonKind_Right], UI_key_zero()) || UI_key_match(ui_state->active_box_key[UI_MouseButtonKind_Right], box->key))) { ui_state->hot_box_key = box->key; sig.flag |= UI_SignalFlag_Hovering; } ////////////////////////////// //- rjf: clicking on something outside the context menu kills the context menu // if(!ctx_menu_is_ancestor && sig.flag & (UI_SignalFlag_LeftPressed|UI_SignalFlag_RightPressed|UI_SignalFlag_MiddlePressed)) { UI_ctx_menu_close(); } return sig; } //////////////////////////////// //~ UI State functions root_function UI_State* UI_state_alloc(void) { ui_g_nil_box.hash_next = &ui_g_nil_box; ui_g_nil_box.hash_prev = &ui_g_nil_box; ui_g_nil_box.first = &ui_g_nil_box; ui_g_nil_box.last = &ui_g_nil_box; ui_g_nil_box.next = &ui_g_nil_box; ui_g_nil_box.prev = &ui_g_nil_box; ui_g_nil_box.parent = &ui_g_nil_box; Arena* arena = m_make_arena_reserve(Gigabytes(1)); UI_State *state = PushArrayZero(arena, UI_State, 1); state->arena = arena; state->box_table_size = 4096; state->box_table = PushArrayZero(arena, UI_BoxSlot, state->box_table_size); // TODO(anton): make some better system for setting and getting theme colors state->colors[UI_Color_Null] = vec4_F32(0, 0, 0, 1); state->colors[UI_Color_PlainBackground] = vec4_F32(0.3f, 0.2f, 0.2f, 1); state->colors[UI_Color_PlainBorder] = vec4_F32(0.4f, 0.4f, 0.4f, 1); state->colors[UI_Color_PlainText] = vec4_F32(0.85f, 0.85f, 0.85f, 1); state->colors[UI_Color_PlainOverlay] = vec4_F32(0.9f, 0.6f, 0.3f, 1); for(U64 index = 0; index < ArrayCount(state->frame_arenas); index += 1) { state->frame_arenas[index] = m_make_arena_reserve(Gigabytes(1)); } state->last_frame_arena_index = 1; state->current_frame_arena_index = 0; // TODO(anton): drag data arena UI_init_stack_nils(state); return state; } root_function void UI_state_set(UI_State *ui) { ui_state = ui; } //////////////////////////////// //~ Build phase root_function void UI_build_begin(OS_Handle window, OS_EventList *events) { //- Reset per frame state { UI_init_stacks(ui_state); ui_state->build_gen += 1; m_arena_clear(UI_frame_arena()); ui_state->root = &ui_g_nil_box; ui_state->ctx_menu_touched_this_frame = 0; ui_state->ctx_menu_changed = 0; } //- Fill per build parameters { ui_state->events = events; ui_state->window = window; ui_state->mouse = OS_mouse_from_window(window);// TODO(anton): window focused and last time moved stuff. } //- "Prune stale boxes" for(U64 slot = 0; slot < ui_state->box_table_size; slot += 1) { for(UI_Box *box = ui_state->box_table[slot].first, *next = 0; !UI_box_is_nil(box); box = next) { // Set the next box next = box->hash_next; // Any boxes existing in the hash table, that has a zero key or has a last_gen_touched+1 that is less // than the current build gen, are removed from the table. The resulting free slots in the table are pushed // onto the free list. if(UI_key_match(box->key, UI_key_zero()) || box->last_gen_touched+1 < ui_state->build_gen) { DLLRemove_NPZ(ui_state->box_table[slot].first, ui_state->box_table[slot].last, box, hash_next, hash_prev, UI_box_is_nil, UI_box_set_nil); StackPush(ui_state->first_free_box, box); ui_state->free_box_list_count += 1; } } } //- Build root // This is a UI box that extends the whole of the window. { Rng2_F32 client_rect = OS_client_rect_from_window(window); Vec2_F32 client_rect_size = dim2_F32(client_rect); UI_set_next_pref_width(UI_pixels(client_rect_size.x, 1)); UI_set_next_pref_height(UI_pixels(client_rect_size.y, 1)); UI_set_next_child_layout_axis(Axis2_Y); String8 root_name = str8_lit("root_box"); UI_Box *root = UI_box_make(0 /* zero box flags for root box */, root_name); UI_push_parent(root); ui_state->root = root; } //- Context menu setup ui_state->ctx_menu_open = ui_state->next_ctx_menu_open; ui_state->ctx_menu_anchor_key = ui_state->next_ctx_menu_anchor_key; { UI_Box *anchor_box = UI_box_from_key(ui_state->ctx_menu_anchor_key); if(!UI_box_is_nil(anchor_box)) { ui_state->ctx_menu_anchor_box_last_pos = anchor_box->rect.p0; } Vec2_F32 anchor = add2_F32(ui_state->ctx_menu_anchor_box_last_pos, ui_state->ctx_menu_anchor_offset); UI_fixed_x(anchor.x) UI_fixed_y(anchor.y) UI_pref_width(UI_size_by_children(0, 1)) UI_pref_height(UI_size_by_children(0, 1)) { UI_set_next_child_layout_axis(Axis2_Y); ui_state->ctx_menu_root = UI_box_make(UI_BoxFlag_FloatingX|UI_BoxFlag_FloatingY, str8_lit("ctx_menu")); } } // Reset hot if we don't have an active widget B32 has_active = 0; for(EachEnumVal(UI_MouseButtonKind, k)) { if(!UI_key_match(ui_state->active_box_key[k], UI_key_zero())) { has_active = 1; } } if(!has_active) { ui_state->hot_box_key = UI_key_zero(); } //- rjf: reset active keys if they have been pruned for(EachEnumVal(UI_MouseButtonKind, k)) { UI_Box *box = UI_box_from_key(ui_state->active_box_key[k]); if(UI_box_is_nil(box)) { ui_state->active_box_key[k] = UI_key_zero(); } } } root_function void UI_build_end(void) { ui_state->ctx_menu_touched_this_frame = 1; if(ui_state->ctx_menu_open != 0 || ui_state->ctx_menu_touched_this_frame == 0) { //UI_ctx_menu_close(); } UI_layout(); // TODO(anton): When I need this and understand why //if(ui_state->ctx_menu_touched_this_frame && !ui_state->ctx_menu_changed) //{ //UI_Box *anchor_box = UI_box_from_key(ui_state->ctx_menu_anchor_key); //if(!UI_box_is_nil(anchor_box)) //{ //Rng2_F32 root_rect = ui_state->ctx_menu_root->rect; //Vec2_F32 pos = //{ //anchor_box->rect.x0 + ui_state->ctx_menu_anchor_offset.x, //anchor_box->rect.y0 + ui_state->ctx_menu_anchor_offset.y, //}; //Vec2_F32 shift = sub2_F32(pos, root_rect.p0); //Rng2_F32 new_root_rect = shift2_F32(root_rect, shift); //ui_state->ctx_menu_root->fixed_position = new_root_rect.p0; //ui_state->ctx_menu_root->fixed_size = dim2_F32(new_root_rect); //ui_state->ctx_menu_root->rect = new_root_rect; //} //} // } //////////////////////////////// //~ Context menu root_function void UI_ctx_menu_open(UI_Key key, UI_Key anchor_key, Vec2_F32 anchor_offset) { anchor_offset.x = (F32)(int)anchor_offset.x; anchor_offset.y = (F32)(int)anchor_offset.y; ui_state->next_ctx_menu_open = 1; ui_state->ctx_menu_changed = 1; // TODO(anton): time parameter for ctx menu animation ui_state->ctx_menu_key = key; ui_state->next_ctx_menu_anchor_key = anchor_key; ui_state->ctx_menu_anchor_offset = anchor_offset; ui_state->ctx_menu_touched_this_frame = 1; ui_state->ctx_menu_anchor_box_last_pos = vec2_F32(100, 100); } root_function void UI_ctx_menu_close(void) { ui_state->next_ctx_menu_open = 0; } root_function B32 UI_begin_ctx_menu(UI_Key key) { UI_push_parent(ui_state->root); UI_push_parent(ui_state->ctx_menu_root); B32 result = UI_key_match(key, ui_state->ctx_menu_key) && ui_state->ctx_menu_open; if(result) { ui_state->ctx_menu_touched_this_frame = 1; ui_state->ctx_menu_root->flags |= UI_BoxFlag_DrawBackground; ui_state->ctx_menu_root->flags |= UI_BoxFlag_DrawDropShadow; UI_push_pref_width(UI_pixels(100, 0)); UI_push_pref_height(UI_pixels(200, 0)); } return result; } root_function B32 UI_ctx_menu_is_open(UI_Key key) { return ui_state->ctx_menu_open && UI_key_match(key, ui_state->ctx_menu_key); } root_function void UI_end_ctx_menu(void) { //UI_Box* top_parent = UI_top_parent(); UI_pop_pref_width(); UI_pop_pref_height(); UI_pop_parent(); //top_parent = UI_top_parent(); UI_pop_parent(); //top_parent = UI_top_parent(); } //////////////////////////////// //~ UI Frame root_function Arena * UI_frame_arena(void) { return ui_state->frame_arenas[ui_state->current_frame_arena_index]; } root_function void UI_frame_begin(F32 delta_time) { ui_g_dt = delta_time; } root_function void UI_frame_end(void) { U32 last = ui_state->last_frame_arena_index; ui_state->last_frame_arena_index = ui_state->current_frame_arena_index; ui_state->current_frame_arena_index = last; } //////////////////////////////// //~ Box hierarchy construction root_function UI_Box * UI_box_from_key(UI_Key key) { UI_Box *result = &ui_g_nil_box; // The box table contains all the hashed boxes, so we mod by the size to get the slot in the table. U64 slot = key.u64[0] % ui_state->box_table_size; if(!UI_key_match(key, UI_key_zero())) { // We check if we have a box corresponding to the key in the hash table, and return that if we find it. for(UI_Box *b = ui_state->box_table[slot].first; !UI_box_is_nil(b); b = b->hash_next) { if(UI_key_match(b->key, key)) { result = b; break; } } } return result; } root_function UI_Box * UI_box_make_from_key(UI_BoxFlags flags, UI_Key key) { // Get the nil box or a box from the hash table. UI_Box *box = UI_box_from_key(key); // If the key has already been used, we "trample" over the key/box pair to make an id-less box. if(box->last_gen_touched == ui_state->build_gen) { box = &ui_g_nil_box; key = UI_key_zero(); } // Allocate the box if it's not allocated, or if it's a duplicate key B32 first_frame = 0; if(UI_box_is_nil(box)) { U64 slot = key.u64[0] % ui_state->box_table_size; first_frame = 1; box = ui_state->first_free_box; // If the first free box is nil we push a new box on the arena. if(UI_box_is_nil(box)) { box = PushArrayZero(ui_state->arena, UI_Box, 1); } else { // If the first free box is non-nil, we pop it from the stack. // This macro works since the first_free_box is a UI_Box type, which has a next member. // So this will but the ui_state->first_free_box pointer to the next member of the previous // element pointed to by ui_state->first_free_box. StackPop(ui_state->first_free_box); MemoryZeroStruct(box); ui_state->free_box_list_count -= 1; } // We push back the box to the doubly linked list in the table slot, using custom functions for // zero check and zero set. DLLPushBack_NPZ(ui_state->box_table[slot].first, ui_state->box_table[slot].last, box, hash_next, hash_prev, UI_box_is_nil, UI_box_set_nil); box->key = key; } //- Link to the tree by getting the parent. If the parent is nil, the current box should actually be the root. UI_Box *parent = UI_top_parent(); if(UI_box_is_nil(parent)) { ui_state->root = box; } else { DLLPushBack_NPZ(parent->first, parent->last, box, next, prev, UI_box_is_nil, UI_box_set_nil); parent->child_count += 1; box->parent = parent; } //- Fill the state of the current box if(!UI_box_is_nil(box)) { if(first_frame) { box->first_gen_touched = ui_state->build_gen; } box->child_count = 0; box->first = box->last = &ui_g_nil_box; box->flags = flags | UI_top_flags(); if(ui_state->fixed_width_stack.top != &ui_state->fixed_width_nil_stack_top) { box->flags |= UI_BoxFlag_FixedWidth; box->fixed_size.x = ui_state->fixed_width_stack.top->v; } else { box->pref_size[Axis2_X] = UI_top_pref_width(); } if(ui_state->fixed_height_stack.top != &ui_state->fixed_height_nil_stack_top) { box->flags |= UI_BoxFlag_FixedHeight; box->fixed_size.y = ui_state->fixed_height_stack.top->v; } else { box->pref_size[Axis2_Y] = UI_top_pref_height(); } box->calc_rel_pos.x = UI_top_fixed_x(); box->calc_rel_pos.y = UI_top_fixed_y(); box->child_layout_axis = UI_top_child_layout_axis(); box->last_gen_touched = ui_state->build_gen; // TODO(anton): Text and color drawing properties here box->background_color = ui_state->colors[UI_Color_PlainBackground]; box->text_color = ui_state->colors[UI_Color_PlainText]; box->border_color = ui_state->colors[UI_Color_PlainBorder]; box->overlay_color = ui_state->colors[UI_Color_PlainOverlay]; } UI_auto_pop_stacks(ui_state); return box; } root_function UI_Box * UI_box_make(UI_BoxFlags flags, String8 string) { UI_Key seed = UI_top_seed_key(); String8 string_hash_part = UI_hash_part_from_box_string(string); UI_Key key = UI_key_from_string(seed, string_hash_part); UI_Box *box = UI_box_make_from_key(flags, key); box->string = str8_copy(UI_frame_arena(), string); return box; } //////////////////////////////// //~ layout root_function void UI_solve_independent_sizes(UI_Box *root, Axis2 axis) { switch(root->pref_size[axis].kind) { default:break; case UI_SizeKind_Pixels: { root->calc_size.v[axis] = root->pref_size[axis].value; root->calc_size.v[axis] = floor_F32(root->calc_size.v[axis]); } break; } // Recurse for(UI_Box *child = root->first; !UI_box_is_nil(child); child = child->next) { UI_solve_independent_sizes(child, axis); } } root_function void UI_solve_upward_dependent_sizes(UI_Box *root, Axis2 axis) { switch(root->pref_size[axis].kind) { default:break; case UI_SizeKind_Percent: { UI_Box *ancestor = &ui_g_nil_box; // Move up the parents and get the first ancestor that does not have // size by children. for(UI_Box *p = root->parent; !UI_box_is_nil(p); p = p->parent) { if(p->pref_size[axis].kind != UI_SizeKind_SizeByChildren) { ancestor = p; break; } } // The calculated size of the argument box to this function is then // the ancestor calculated size, scaled by the preferred size of this box, which is // assumed to be given as a percentage by the SizeKind if(!UI_box_is_nil(ancestor)) { root->calc_size.v[axis] = ancestor->calc_size.v[axis] * root->pref_size[axis].value; root->calc_size.v[axis] = floor_F32(root->calc_size.v[axis]); } } break; } for(UI_Box *child = root->first; !UI_box_is_nil(child); child = child->next) { UI_solve_upward_dependent_sizes(child, axis); } } root_function void UI_solve_downward_dependent_sizes(UI_Box *root, Axis2 axis) { // Here we first recurse since we will depend on the result for this input. for(UI_Box *child = root->first; !UI_box_is_nil(child); child = child->next) { UI_solve_downward_dependent_sizes(child, axis); } switch(root->pref_size[axis].kind) { default:break; case UI_SizeKind_SizeByChildren: { F32 value = 0; { // We will calculate the size to be the sum of the child sizes on this axis, // if we are on the layout axis. // If we are not on the layout axis the size will just be the maximum size of any child on that axis. if(axis == root->child_layout_axis) { for(UI_Box *child = root->first; !UI_box_is_nil(child); child = child->next) { value += child->calc_size.v[axis]; } } else { for(UI_Box *child = root->first; !UI_box_is_nil(child); child = child->next) { value = Max(value, child->calc_size.v[axis]); } } } root->calc_size.v[axis] = value; root->calc_size.v[axis] = floor_F32(root->calc_size.v[axis]); } break; } } root_function void UI_solve_size_violations(UI_Box *root, Axis2 axis) { // Determine maximum available space from the root size. F32 available_space = root->calc_size.v[axis]; F32 taken_space = 0.0f; F32 total_fix_budget = 0.0f; B32 is_overflow_x_on_axis = (root->flags & (UI_BoxFlag_OverflowX<first; !UI_box_is_nil(child); child = child->next) { if(!(child->flags & (UI_BoxFlag_FloatingX<child_layout_axis) { taken_space += child->calc_size.v[axis]; } else { taken_space = Max(taken_space, child->calc_size.v[axis]); } F32 fix_budget_this_child = child->calc_size.v[axis] * (1.0f - child->pref_size[axis].strictness); total_fix_budget += fix_budget_this_child; } } } //- Fix children as much as possible within the calculated budget if(!is_overflow_x_on_axis) { F32 violation = taken_space - available_space; if(violation > 0 && total_fix_budget > 0) { for(UI_Box *child = root->first; !UI_box_is_nil(child); child = child->next) { if(!(child->flags & (UI_BoxFlag_FloatingX<calc_size.v[axis] * (1.0f - child->pref_size[axis].strictness); F32 fix_size_this_child = 0.0f; if(axis == root->child_layout_axis) { fix_size_this_child = fix_budget_this_child * (violation / total_fix_budget); } else { fix_size_this_child = child->calc_size.v[axis] - available_space; } fix_size_this_child = Clamp(0, fix_size_this_child, fix_budget_this_child); child->calc_size.v[axis] -= fix_size_this_child; child->calc_size.v[axis] = floor_F32(child->calc_size.v[axis]); } } } } //- Position all children after fixup { if(axis == root->child_layout_axis) { // Determine the relative offset by incrementing p by the size of a child on the relevant axis. F32 p = 0.0f; for(UI_Box *child = root->first; !UI_box_is_nil(child); child = child->next) { if(!(child->flags & (UI_BoxFlag_FloatingX<calc_rel_pos.v[axis] = p; p += child->calc_size.v[axis]; } } } else { // If we're not on the layout axis the relative position is just zero for(UI_Box *child = root->first; !UI_box_is_nil(child); child = child->next) { if(!(child->flags & (UI_BoxFlag_FloatingX<calc_rel_pos.v[axis] = 0; } } } // Set the actual position values and rectangles for(UI_Box *child = root->first; !UI_box_is_nil(child); child = child->next) { Rng2_F32 last_rel_rect = child->rel_rect; unused_variable(last_rel_rect); // The relative rectangle starts at the relative position, and ends at the relative rectangle p0 + size. // TODO(anton): What does the relative rectangle mean? child->rel_rect.p0.v[axis] = child->calc_rel_pos.v[axis]; child->rel_rect.p1.v[axis] = child->rel_rect.p0.v[axis] + child->calc_size.v[axis]; // TODO(anton): Corner stuff here // The actual rectangle is the root rect p0, plus th relative p0, minus view offset of the root. // And the p1 is the p0 + the size of the child. child->rect.p0.v[axis] = root->rect.p0.v[axis] + child->rel_rect.p0.v[axis] - root->view_offset.v[axis]; child->rect.p1.v[axis] = child->rect.p0.v[axis] + child->calc_size.v[axis]; if(!(child->flags & (UI_BoxFlag_FloatingX<rect.p0.v[axis] = floor_F32(child->rect.p0.v[axis]); child->rect.p1.v[axis] = floor_F32(child->rect.p1.v[axis]); } } } // Recurse for(UI_Box *child = root->first; !UI_box_is_nil(child); child = child->next) { UI_solve_size_violations(child, axis); } } root_function void UI_layout_root(UI_Box *root, Axis2 axis) { UI_solve_independent_sizes(root, axis); UI_solve_upward_dependent_sizes(root, axis); UI_solve_downward_dependent_sizes(root, axis); UI_solve_size_violations(root, axis); } root_function void UI_layout(void) { for(Axis2 axis = (Axis2)0; axis < Axis2_COUNT; axis = (Axis2)(axis+1)) { UI_layout_root(ui_state->root, axis); } } //////////////////////////////// //~ Compositions root_function void UI_push_pref_size(Axis2 axis, UI_Size v) { if(axis == Axis2_X) { UI_push_pref_width(v); } else { UI_push_pref_height(v); } } root_function void UI_pop_pref_size(Axis2 axis) { if(axis == Axis2_X) { UI_pop_pref_width(); } else { UI_pop_pref_height(); } } root_function void UI_set_next_pref_size(Axis2 axis, UI_Size v) { if(axis == Axis2_X) { UI_set_next_pref_width(v); } else { UI_set_next_pref_height(v); } } root_function void UI_push_fixed_pos(Vec2_F32 v) { UI_push_fixed_x(v.x); UI_push_fixed_y(v.y); } root_function void UI_pop_fixed_pos() { UI_pop_fixed_x(); UI_pop_fixed_y(); } root_function void UI_set_next_fixed_pos(Vec2_F32 v) { UI_set_next_fixed_x(v.x); UI_set_next_fixed_y(v.y); } root_function void UI_push_fixed_rect(Rng2_F32 rect) { Vec2_F32 dim = dim2_F32(rect); UI_push_fixed_pos(rect.p0); UI_push_pref_size(Axis2_X, UI_pixels(dim.x, 1)); UI_push_pref_size(Axis2_Y, UI_pixels(dim.y, 1)); } root_function void UI_pop_fixed_rect() { UI_pop_fixed_pos(); UI_pop_pref_size(Axis2_X); UI_pop_pref_size(Axis2_Y); } root_function void UI_set_next_fixed_rect(Rng2_F32 rect) { Vec2_F32 dim = dim2_F32(rect); UI_set_next_fixed_pos(rect.p0); UI_set_next_pref_size(Axis2_X, UI_pixels(dim.x, 1)); UI_set_next_pref_size(Axis2_Y, UI_pixels(dim.y, 1)); } root_function Rng2_F32 UI_push_rect(Rng2_F32 rect) { Rng2_F32 replaced = {0}; Vec2_F32 size = dim2_F32(rect); replaced.x0 = UI_push_fixed_x(rect.x0); replaced.y0 = UI_push_fixed_y(rect.y0); replaced.x1 = replaced.x0 + UI_push_fixed_width(size.x); replaced.y1 = replaced.y0 + UI_push_fixed_height(size.y); return replaced; } root_function Rng2_F32 UI_pop_rect(void) { Rng2_F32 popped = {0}; popped.x0 = UI_pop_fixed_x(); popped.y0 = UI_pop_fixed_y(); popped.x1 = popped.x0 + UI_pop_fixed_width(); popped.y1 = popped.y0 + UI_pop_fixed_height(); return popped; } //////////////////////////////// //~ Drawing and text root_function Vec2_F32 UI_text_pos_from_box(UI_Box *box) { // The stb assumes that the supplied start position will give // the _BASELINE_ of the text that is to be produced. // So the text position we give here is adjusted to x of the box rect's p0, // and to the y of the box rect's p1. F32 offset_x_pixels = 0.1f*(box->rect.x1-box->rect.x0); F32 offset_y_pixels = 0.30*(box->rect.y1-box->rect.y0); Vec2_F32 result = {0}; result.x = box->rect.x0 + offset_x_pixels; result.y = box->rect.y1 - offset_y_pixels; return result; } root_function String8 UI_display_string_from_box(UI_Box *box) { return box->string; }