873 lines
25 KiB
C
873 lines
25 KiB
C
|
////////////////////////////////
|
||
|
// NOTE(allen): Colors
|
||
|
|
||
|
global v3 cl_black = {0.f, 0.f, 0.f};
|
||
|
global v3 cl_white = {1.f, 1.f, 1.f};
|
||
|
#define cl_gray(f) v3((f), (f), (f))
|
||
|
|
||
|
global v3 cl_red = {1.f, 0.f, 0.f};
|
||
|
global v3 cl_yellow = {1.f, 1.f, 0.f};
|
||
|
global v3 cl_green = {0.f, 1.f, 0.f};
|
||
|
global v3 cl_cyan = {0.f, 1.f, 1.f};
|
||
|
global v3 cl_blue = {0.f, 0.f, 1.f};
|
||
|
global v3 cl_awful = {1.f, 0.f, 1.f};
|
||
|
|
||
|
////////////////////////////////
|
||
|
|
||
|
global v3 cl_back_editor = {0.15f, 0.15f, 0.15f};
|
||
|
|
||
|
global v3 cl_back_unimportant = {0.00f, 0.00f, 0.00f};
|
||
|
global v3 cl_back = {0.21f, 0.21f, 0.21f};
|
||
|
global v3 cl_back_flash = {0.50f, 0.50f, 0.50f};
|
||
|
|
||
|
global v3 cl_outline_unimportant = {0.15f, 0.30f, 0.15f};
|
||
|
global v3 cl_outline_important = {0.09f, 0.50f, 0.09f};
|
||
|
global v3 cl_outline = {0.00f, 0.70f, 0.00f};
|
||
|
global v3 cl_outline_flash = {0.50f, 0.85f, 0.50f};
|
||
|
|
||
|
global v3 cl_button_text = {1.f, 1.f, 0.8f};
|
||
|
global v3 cl_button_text_flash = {0.f, 0.f, 0.2f};
|
||
|
|
||
|
|
||
|
global v3 cl_tab_outline_unimportant = {0.15f, 0.15f, 0.15f};
|
||
|
global v3 cl_tab_outline = {0.50f, 0.50f, 0.50f};
|
||
|
|
||
|
|
||
|
global v3 cl_err_back_unimportant = {0.100f, 0.00f, 0.00f};
|
||
|
global v3 cl_err_back = {0.299f, 0.21f, 0.21};
|
||
|
global v3 cl_err_back_flash = {0.550f, 0.50f, 0.50f};
|
||
|
|
||
|
global v3 cl_err_outline_unimportant = {0.30f, 0.195f, 0.15f};
|
||
|
global v3 cl_err_outline_important = {0.50f, 0.213f, 0.09f};
|
||
|
global v3 cl_err_outline = {0.70f, 0.210f, 0.00f};
|
||
|
global v3 cl_err_outline_flash = {0.85f, 0.605f, 0.50f};
|
||
|
|
||
|
|
||
|
global v3 cl_page_back_unimportant = {0.100f, 0.000f, 0.00f};
|
||
|
global v3 cl_page_back = {0.299f, 0.299f, 0.21f};
|
||
|
global v3 cl_page_back_flash = {0.550f, 0.550f, 0.50f};
|
||
|
|
||
|
global v3 cl_page_outline_unimportant = {0.30f, 0.30f, 0.15f};
|
||
|
global v3 cl_page_outline_important = {0.50f, 0.50f, 0.09f};
|
||
|
global v3 cl_page_outline = {0.70f, 0.70f, 0.00f};
|
||
|
global v3 cl_page_outline_flash = {0.85f, 0.85f, 0.50f};
|
||
|
|
||
|
|
||
|
global v3 cl_test_back_unimportant = {0.050f, 0.000f, 0.050f};
|
||
|
global v3 cl_test_back = {0.150f, 0.105f, 0.150f};
|
||
|
global v3 cl_test_back_flash = {0.275f, 0.250f, 0.275f};
|
||
|
|
||
|
global v3 cl_test_outline_unimportant = {0.200f, 0.130f, 0.200f};
|
||
|
global v3 cl_test_outline_important = {0.250f, 0.106f, 0.250f};
|
||
|
global v3 cl_test_outline = {0.350f, 0.105f, 0.350f};
|
||
|
global v3 cl_test_outline_flash = {0.425f, 0.303f, 0.425f};
|
||
|
|
||
|
|
||
|
global v3 cl_menu_bar = {0.70f, 0.70f, 0.70f};
|
||
|
global v3 cl_error = {1.00f, 0.00f, 0.00f};
|
||
|
global v3 cl_text = {1.00f, 1.00f, 1.00f};
|
||
|
global v3 cl_comment = {0.50f, 0.50f, 0.65f};
|
||
|
global v3 cl_keyword = {0.95f, 0.99f, 0.50f};
|
||
|
|
||
|
////////////////////////////////
|
||
|
|
||
|
internal UI_ColorProfile*
|
||
|
UI_ColorsDefault(void){
|
||
|
local_persist UI_ColorProfile result = {0};
|
||
|
local_persist b32 first = 1;
|
||
|
if (first){
|
||
|
first = 0;
|
||
|
result.back[0] = cl_back_unimportant;
|
||
|
result.back[1] = cl_back;
|
||
|
result.back[2] = cl_back;
|
||
|
result.back[3] = cl_back_flash;
|
||
|
result.outline[0] = cl_outline_unimportant;
|
||
|
result.outline[1] = cl_outline_important;
|
||
|
result.outline[2] = cl_outline;
|
||
|
result.outline[3] = cl_outline_flash;
|
||
|
result.front[0] = cl_button_text;
|
||
|
result.front[1] = cl_button_text;
|
||
|
result.front[2] = cl_button_text;
|
||
|
result.front[3] = cl_button_text_flash;
|
||
|
}
|
||
|
return(&result);
|
||
|
}
|
||
|
|
||
|
internal UI_ColorProfile*
|
||
|
UI_ColorsProblem(void){
|
||
|
local_persist UI_ColorProfile result = {0};
|
||
|
local_persist b32 first = 1;
|
||
|
if (first){
|
||
|
first = 0;
|
||
|
result.back[0] = cl_err_back_unimportant;
|
||
|
result.back[1] = cl_err_back;
|
||
|
result.back[2] = cl_err_back;
|
||
|
result.back[3] = cl_err_back_flash;
|
||
|
result.outline[0] = cl_err_outline_unimportant;
|
||
|
result.outline[1] = cl_err_outline_important;
|
||
|
result.outline[2] = cl_err_outline;
|
||
|
result.outline[3] = cl_err_outline_flash;
|
||
|
result.front[0] = cl_button_text;
|
||
|
result.front[1] = cl_button_text;
|
||
|
result.front[2] = cl_button_text;
|
||
|
result.front[3] = cl_button_text_flash;
|
||
|
}
|
||
|
return(&result);
|
||
|
}
|
||
|
|
||
|
internal UI_ColorProfile*
|
||
|
UI_ColorsTabs(void){
|
||
|
local_persist UI_ColorProfile result = {0};
|
||
|
local_persist b32 first = 1;
|
||
|
if (first){
|
||
|
first = 0;
|
||
|
result.back[0] = cl_back_unimportant;
|
||
|
result.back[1] = cl_back_editor;
|
||
|
result.back[2] = cl_back;
|
||
|
result.back[3] = cl_back;
|
||
|
result.outline[0] = cl_tab_outline_unimportant;
|
||
|
result.outline[1] = cl_back_editor;
|
||
|
result.outline[2] = cl_tab_outline;
|
||
|
result.outline[3] = cl_tab_outline;
|
||
|
for (u64 i = 0; i < (u64)UI_ActionLevel_COUNT; i += 1){
|
||
|
result.front[i] = cl_button_text;
|
||
|
}
|
||
|
}
|
||
|
return(&result);
|
||
|
}
|
||
|
|
||
|
internal UI_ColorProfile*
|
||
|
UI_ColorsPage(void){
|
||
|
local_persist UI_ColorProfile result = {0};
|
||
|
local_persist b32 first = 1;
|
||
|
if (first){
|
||
|
first = 0;
|
||
|
result.back[0] = cl_page_back_unimportant;
|
||
|
result.back[1] = cl_page_back;
|
||
|
result.back[2] = cl_page_back;
|
||
|
result.back[3] = cl_page_back_flash;
|
||
|
result.outline[0] = cl_page_outline_unimportant;
|
||
|
result.outline[1] = cl_page_outline_important;
|
||
|
result.outline[2] = cl_page_outline;
|
||
|
result.outline[3] = cl_page_outline_flash;
|
||
|
result.front[0] = cl_button_text;
|
||
|
result.front[1] = cl_button_text;
|
||
|
result.front[2] = cl_button_text;
|
||
|
result.front[3] = cl_button_text_flash;
|
||
|
}
|
||
|
return(&result);
|
||
|
}
|
||
|
|
||
|
internal UI_ColorProfile*
|
||
|
UI_ColorsTest(void){
|
||
|
local_persist UI_ColorProfile result = {0};
|
||
|
local_persist b32 first = 1;
|
||
|
if (first){
|
||
|
first = 0;
|
||
|
result.back[0] = cl_test_back_unimportant;
|
||
|
result.back[1] = cl_test_back;
|
||
|
result.back[2] = cl_test_back;
|
||
|
result.back[3] = cl_test_back_flash;
|
||
|
result.outline[0] = cl_test_outline_unimportant;
|
||
|
result.outline[1] = cl_test_outline_important;
|
||
|
result.outline[2] = cl_test_outline;
|
||
|
result.outline[3] = cl_test_outline_flash;
|
||
|
result.front[0] = cl_button_text;
|
||
|
result.front[1] = cl_button_text;
|
||
|
result.front[2] = cl_button_text;
|
||
|
result.front[3] = cl_button_text_flash;
|
||
|
}
|
||
|
return(&result);
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
UI_SetRedText(UI_ColorProfile *cl){
|
||
|
for (u64 i = 0; i < (u64)UI_ActionLevel_COUNT; i += 1){
|
||
|
cl->front[i] = cl_red;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal v3
|
||
|
UI_Grayified(v3 color){
|
||
|
v3 hsv = RGBToHSV(color);
|
||
|
hsv.y *= 0.5f;
|
||
|
hsv.z *= 0.4f;
|
||
|
v3 result = HSVToRGB(hsv);
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
////////////////////////////////
|
||
|
// NOTE(allen): Event Helpers
|
||
|
|
||
|
internal b32
|
||
|
UI_MouseInRect(Rect rect){
|
||
|
b32 result = 0;
|
||
|
if (APP_MouseIsActive()){
|
||
|
Rect clipped = RectIntersect(rect, R_GetClip());
|
||
|
result = RectContains(clipped, vars->mouse_p);
|
||
|
}
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
internal b32
|
||
|
UI_TryGetLeftClick(OS_Event **event_out){
|
||
|
b32 result = 0;
|
||
|
OS_Event *event = 0;
|
||
|
for (;OS_GetNextEvent(&event);){
|
||
|
if (event->type == OS_EventType_MousePress && event->mouse_button == MouseButton_Left){
|
||
|
*event_out = event;
|
||
|
result = 1;
|
||
|
}
|
||
|
}
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
internal b32
|
||
|
UI_TryEatLeftClick(void){
|
||
|
OS_Event *event = 0;
|
||
|
b32 result = UI_TryGetLeftClick(&event);
|
||
|
if (result){
|
||
|
OS_EatEvent(event);
|
||
|
}
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
internal b32
|
||
|
UI_TryGetKeyPress(OS_Event **event_out, Key key){
|
||
|
b32 result = 0;
|
||
|
OS_Event *event = 0;
|
||
|
for (;OS_GetNextEvent(&event);){
|
||
|
if (event->type == OS_EventType_KeyPress && event->key == key){
|
||
|
*event_out = event;
|
||
|
result = 1;
|
||
|
}
|
||
|
}
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
internal b32
|
||
|
UI_TryEatKeyPress(Key key){
|
||
|
OS_Event *event = 0;
|
||
|
b32 result = UI_TryGetKeyPress(&event, key);
|
||
|
if (result){
|
||
|
OS_EatEvent(event);
|
||
|
}
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
internal b32
|
||
|
UI_TryGetKeyPressModified(OS_Event **event_out, Key key, KeyModifiers mods){
|
||
|
b32 result = 0;
|
||
|
OS_Event *event = 0;
|
||
|
for (;OS_GetNextEvent(&event);){
|
||
|
if (event->type == OS_EventType_KeyPress && event->key == key && event->modifiers == mods){
|
||
|
*event_out = event;
|
||
|
result = 1;
|
||
|
}
|
||
|
}
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
internal b32
|
||
|
UI_TryEatKeyPressModified(Key key, KeyModifiers mods){
|
||
|
OS_Event *event = 0;
|
||
|
b32 result = UI_TryGetKeyPressModified(&event, key, mods);
|
||
|
if (result){
|
||
|
OS_EatEvent(event);
|
||
|
}
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
internal b32
|
||
|
UI_TryGetScroll(OS_Event **event_out){
|
||
|
b32 result = 0;
|
||
|
OS_Event *event = 0;
|
||
|
for (;OS_GetNextEvent(&event);){
|
||
|
if (event->type == OS_EventType_MouseScroll){
|
||
|
*event_out = event;
|
||
|
result = 1;
|
||
|
}
|
||
|
}
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
internal b32
|
||
|
UI_TryEatScroll(v2 *scroll_out){
|
||
|
OS_Event *event = 0;
|
||
|
b32 result = UI_TryGetScroll(&event);
|
||
|
if (result){
|
||
|
*scroll_out = event->scroll;
|
||
|
OS_EatEvent(event);
|
||
|
}
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
internal String8
|
||
|
UI_StringizeKeyModified(M_Arena *arena, Key key, KeyModifiers mods){
|
||
|
String8_List list = {0};
|
||
|
if (mods & KeyModifier_Ctrl){
|
||
|
StringListPush(arena, &list, S8Lit("ctrl-"));
|
||
|
}
|
||
|
if (mods & KeyModifier_Shift){
|
||
|
StringListPush(arena, &list, S8Lit("shift-"));
|
||
|
}
|
||
|
if (mods & KeyModifier_Alt){
|
||
|
StringListPush(arena, &list, S8Lit("alt-"));
|
||
|
}
|
||
|
String8 key_name = KeyName(key);
|
||
|
StringListPush(arena, &list, key_name);
|
||
|
return(StringListJoin(arena, &list, 0));
|
||
|
}
|
||
|
|
||
|
////////////////////////////////
|
||
|
// NOTE(allen): UI ID
|
||
|
|
||
|
internal b32
|
||
|
UI_IdEq(UI_Id a, UI_Id b){
|
||
|
return(MemoryMatchStruct(&a, &b));
|
||
|
}
|
||
|
|
||
|
internal UI_Id
|
||
|
UI_IdZero(void){
|
||
|
UI_Id id = {0};
|
||
|
return(id);
|
||
|
}
|
||
|
|
||
|
internal UI_Id
|
||
|
UI_IdV(u64 v){
|
||
|
UI_Id id;
|
||
|
id.v1 = v;
|
||
|
return(id);
|
||
|
}
|
||
|
|
||
|
internal UI_Id
|
||
|
UI_IdP(void *p){
|
||
|
UI_Id id;
|
||
|
id.p1 = p;
|
||
|
return(id);
|
||
|
}
|
||
|
|
||
|
////////////////////////////////
|
||
|
// NOTE(allen): Button placer
|
||
|
|
||
|
internal void
|
||
|
UI_SetColorProfile(UI_ButtonCtx *ctx, UI_ColorProfile *profile){
|
||
|
MemoryCopyStruct(&ctx->cl, profile);
|
||
|
}
|
||
|
|
||
|
internal UI_ButtonCtx
|
||
|
UI_InitButtonCtx(Rect rect, v2 btn_dim, R_Font *font, UI_Id id){
|
||
|
UI_ButtonCtx result = {0};
|
||
|
|
||
|
v2 rect_dim = RectGetDim(rect);
|
||
|
if (rect_dim.x >= btn_dim.x && rect_dim.y >= btn_dim.y){
|
||
|
result.font = font;
|
||
|
result.id = id;
|
||
|
result.button_id.v1 = 1;
|
||
|
result.rect = rect;
|
||
|
result.p = rect.p0;
|
||
|
result.btn_dim = btn_dim;
|
||
|
result.has_room = 1;
|
||
|
|
||
|
result.condition = 1;
|
||
|
result.text_scale = 1;
|
||
|
|
||
|
UI_SetColorProfile(&result, UI_ColorsDefault());
|
||
|
}
|
||
|
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
internal f32
|
||
|
UI_ButtonCtxGetTraversedY(UI_ButtonCtx *ctx){
|
||
|
return(ctx->p.y - ctx->rect.y0);
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
UI_NextCondition(UI_ButtonCtx *ctx, b32 condition){
|
||
|
ctx->condition = condition;
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
UI_NextHotkey(UI_ButtonCtx *ctx, Key key, KeyModifiers mods){
|
||
|
ctx->has_hot_key = 1;
|
||
|
ctx->hot_key = key;
|
||
|
ctx->hot_key_mods = mods;
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
UI_NextTooltip(UI_ButtonCtx *ctx, String8 string){
|
||
|
ctx->tool_tip = string;
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
UI_NextActive(UI_ButtonCtx *ctx, b32 active){
|
||
|
ctx->active = active;
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
_UI_ButtonHotkey(UI_ButtonCtx *ctx, b32 *result_out){
|
||
|
if (ctx->enable_hot_keys && ctx->has_hot_key && ctx->condition){
|
||
|
if (UI_TryEatKeyPressModified(ctx->hot_key, ctx->hot_key_mods)){
|
||
|
*result_out = 1;
|
||
|
APP_ZeroOwnershipOfFloatingWindow();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal f32
|
||
|
_UI_ButtonGetXAdvance(UI_ButtonCtx *ctx){
|
||
|
f32 advance = 0;
|
||
|
if (ctx->enable_flexible_x_advance){
|
||
|
advance = ClampTop(ctx->this_button_x_advance, ctx->btn_dim.x);
|
||
|
}
|
||
|
else{
|
||
|
advance = ctx->btn_dim.x;
|
||
|
}
|
||
|
return(advance);
|
||
|
}
|
||
|
|
||
|
internal b32
|
||
|
_UI_ButtonGetNextP(UI_ButtonCtx *ctx, v2 p, v2 *out){
|
||
|
b32 result = 1;
|
||
|
v2 dim = ctx->btn_dim;
|
||
|
p.x += _UI_ButtonGetXAdvance(ctx);
|
||
|
if (p.x + dim.x > ctx->rect.x1){
|
||
|
p.x = ctx->rect.x0;
|
||
|
p.y += dim.y;
|
||
|
if (p.y + dim.y > ctx->rect.y1){
|
||
|
result = 0;
|
||
|
}
|
||
|
}
|
||
|
*out = p;
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
internal b32
|
||
|
_UI_ButtonNextWillFit(UI_ButtonCtx *ctx){
|
||
|
b32 result = 1;
|
||
|
v2 p = ctx->p;
|
||
|
if (!_UI_ButtonGetNextP(ctx, p, &p)){
|
||
|
result = 0;
|
||
|
}
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
internal Rect
|
||
|
_UI_ButtonPre(UI_ButtonCtx *ctx, UI_ActionLevel *action_level_out, b32 *result_out){
|
||
|
// NOTE(allen): Drop down check
|
||
|
ctx->do_drop_down = (ctx->enable_drop_down && !_UI_ButtonNextWillFit(ctx));
|
||
|
if (ctx->do_drop_down){
|
||
|
ctx->restore.condition = ctx->condition;
|
||
|
ctx->restore.active = ctx->active;
|
||
|
ctx->restore.has_hot_key = ctx->has_hot_key;
|
||
|
ctx->restore.tool_tip = ctx->tool_tip;
|
||
|
ctx->condition = 1;
|
||
|
ctx->active = 0;
|
||
|
ctx->has_hot_key = 0;
|
||
|
ctx->tool_tip = S8Lit("more");
|
||
|
ctx->has_room = 0;
|
||
|
}
|
||
|
|
||
|
// NOTE(allen): Layout
|
||
|
f32 right = ctx->p.x + _UI_ButtonGetXAdvance(ctx);;
|
||
|
Rect rect = MakeRect(V2Expand(ctx->p), right, ctx->p.y + ctx->btn_dim.y);
|
||
|
|
||
|
// NOTE(allen): Mouse
|
||
|
UI_ActionLevel action_level = UI_ActionLevel_None;
|
||
|
if (ctx->active){
|
||
|
action_level = UI_ActionLevel_Active;
|
||
|
}
|
||
|
ctx->did_tool_tip = 0;
|
||
|
if (UI_MouseInRect(rect)){
|
||
|
if (ctx->condition){
|
||
|
action_level = UI_ActionLevel_Hover;
|
||
|
if (UI_TryEatLeftClick()){
|
||
|
*result_out = 1;
|
||
|
action_level = UI_ActionLevel_Flash;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// NOTE(allen): Tool tip
|
||
|
{
|
||
|
M_Arena *scratch = OS_GetScratch();
|
||
|
String8_List tool_tip = {0};
|
||
|
StringListPush(scratch, &tool_tip, ctx->tool_tip);
|
||
|
if (ctx->has_hot_key){
|
||
|
StringListPush(scratch, &tool_tip, S8Lit(" ["));
|
||
|
String8 key_str = UI_StringizeKeyModified(scratch, ctx->hot_key, ctx->hot_key_mods);
|
||
|
StringListPush(scratch, &tool_tip, key_str);
|
||
|
StringListPush(scratch, &tool_tip, S8Lit("]"));
|
||
|
}
|
||
|
String8 tool_tip_str = StringListJoin(APP_GetFrameArena(), &tool_tip, 0);
|
||
|
APP_SetToolTip(tool_tip_str);
|
||
|
OS_ReleaseScratch(scratch);
|
||
|
|
||
|
if (tool_tip_str.size > 0){
|
||
|
ctx->did_tool_tip = 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
*action_level_out = action_level;
|
||
|
|
||
|
// NOTE(allen): Render back
|
||
|
R_SelectFont(ctx->font);
|
||
|
v3 back = ctx->cl.back[action_level];
|
||
|
v3 outline = ctx->cl.outline[action_level];
|
||
|
if (!ctx->condition){
|
||
|
back = UI_Grayified(back);
|
||
|
outline = UI_Grayified(outline);
|
||
|
}
|
||
|
R_Rect(rect, back, 1.f);
|
||
|
R_RectOutline(rect, ctx->outline_t, outline, 1.f);
|
||
|
|
||
|
return(rect);
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
_UI_ButtonPost(UI_ButtonCtx *ctx){
|
||
|
if (!_UI_ButtonGetNextP(ctx, ctx->p, &ctx->p)){
|
||
|
ctx->has_room = 0;
|
||
|
}
|
||
|
|
||
|
if (ctx->do_drop_down){
|
||
|
ctx->condition = ctx->restore.condition;
|
||
|
ctx->active = ctx->restore.active;
|
||
|
ctx->has_hot_key = ctx->restore.has_hot_key;
|
||
|
ctx->tool_tip = ctx->restore.tool_tip;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
_UI_ButtonEatExtendedParameters(UI_ButtonCtx *ctx){
|
||
|
ctx->condition = 1;
|
||
|
ctx->active = 0;
|
||
|
ctx->has_hot_key = 0;
|
||
|
ctx->tool_tip.size = 0;
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
_UI_DropDownButtonClick(UI_ButtonCtx *ctx, b32 click_result){
|
||
|
if (ctx->enable_hot_keys){
|
||
|
if (UI_TryEatKeyPressModified(Key_ForwardSlash, KeyModifier_Ctrl)){
|
||
|
APP_TakeOwnershipOfFloatingWindow(ctx->id);
|
||
|
}
|
||
|
}
|
||
|
if (click_result){
|
||
|
APP_TakeOwnershipOfFloatingWindow(ctx->id);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
UI_ButtonDropdownCallback(void *ptr, APP_FloatingWindowResult *result);
|
||
|
|
||
|
internal UI_ButtonRecord*
|
||
|
_UI_DropDownButtonSaveRecord(UI_ButtonCtx *ctx, Rect rect, b32 *result_out){
|
||
|
M_Arena *arena = APP_GetFrameArena();
|
||
|
|
||
|
UI_ButtonDropdown *last_frame_dropdown = (UI_ButtonDropdown*)APP_GetLastFrameFloatingWindowPtr();
|
||
|
if (last_frame_dropdown != 0){
|
||
|
UI_ButtonRecord *activated = last_frame_dropdown->activated;
|
||
|
if (activated != 0 && UI_IdEq(activated->id, ctx->button_id)){
|
||
|
*result_out = 1;
|
||
|
APP_ZeroOwnershipOfFloatingWindow();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
UI_ButtonDropdown *dropdown = (UI_ButtonDropdown*)APP_GetFloatingWindowPtr();
|
||
|
if (dropdown == 0){
|
||
|
dropdown = PushArrayZero(arena, UI_ButtonDropdown, 1);
|
||
|
dropdown->font = ctx->font;
|
||
|
dropdown->btn_dim = ctx->btn_dim;
|
||
|
dropdown->source_rect = rect;
|
||
|
APP_SetFloatingWindowPtrAndCallback(dropdown, UI_ButtonDropdownCallback);
|
||
|
}
|
||
|
|
||
|
UI_ButtonRecord *record = PushArray(arena, UI_ButtonRecord, 1);
|
||
|
record->condition = ctx->condition;
|
||
|
record->has_hot_key = ctx->has_hot_key;
|
||
|
record->hot_key = ctx->hot_key;
|
||
|
record->hot_key_mods = ctx->hot_key_mods;
|
||
|
record->outline_t = ctx->outline_t;
|
||
|
record->text_scale = ctx->text_scale;
|
||
|
MemoryCopyStruct(&record->cl, &ctx->cl);
|
||
|
record->tool_tip = ctx->tool_tip;
|
||
|
record->id = ctx->button_id;
|
||
|
|
||
|
SLLQueuePush(dropdown->first, dropdown->last, record);
|
||
|
dropdown->count += 1;
|
||
|
|
||
|
return(record);
|
||
|
}
|
||
|
|
||
|
internal b32
|
||
|
UI_Button(UI_ButtonCtx *ctx, u8 major, u8 minor){
|
||
|
b32 result = 0;
|
||
|
|
||
|
_UI_ButtonHotkey(ctx, &result);
|
||
|
|
||
|
b32 did_button = ctx->has_room;
|
||
|
Rect rect = {0};
|
||
|
if (ctx->has_room){
|
||
|
b32 click_result = 0;
|
||
|
UI_ActionLevel action_level;
|
||
|
rect = _UI_ButtonPre(ctx, &action_level, &click_result);
|
||
|
|
||
|
|
||
|
|
||
|
String8 on_screen_major = S8(&major, 1);
|
||
|
String8 on_screen_minor = S8(&minor, 1);
|
||
|
v3 text_color = ctx->cl.front[action_level];
|
||
|
|
||
|
if (!ctx->condition){
|
||
|
text_color = UI_Grayified(text_color);
|
||
|
}
|
||
|
|
||
|
if (ctx->do_drop_down){
|
||
|
_UI_DropDownButtonClick(ctx, click_result);
|
||
|
on_screen_major = S8Lit("/");
|
||
|
on_screen_minor = S8Lit(" ");
|
||
|
text_color = cl_button_text;
|
||
|
did_button = 0;
|
||
|
}
|
||
|
else{
|
||
|
if (click_result){
|
||
|
result = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
f32 major_scale = ctx->text_scale;
|
||
|
f32 minor_scale = ctx->text_scale*0.65f;
|
||
|
|
||
|
v2 major_dim = R_StringDim(major_scale, on_screen_major);
|
||
|
v2 minor_dim = R_StringDim(minor_scale, on_screen_minor);
|
||
|
|
||
|
if (on_screen_minor.str[0] == ' '){
|
||
|
minor_dim.x = 0.f;
|
||
|
minor_dim.y = 0.f;
|
||
|
}
|
||
|
|
||
|
v2 major_p = {0};
|
||
|
v2 minor_p = {0};
|
||
|
minor_p.x = major_p.x + 0.8f*major_dim.x;
|
||
|
// align 0.7 of the way down major with 0.5 of the way down minor
|
||
|
// solve for minor_y in (major_y + 0.7*major_h = minor_y + 0.5*minor_h)
|
||
|
minor_p.y = major_p.y + 0.7f*major_dim.y - 0.5f*minor_dim.y;
|
||
|
|
||
|
v2 combined_dim;
|
||
|
combined_dim.x = Max(major_p.x + major_dim.x, minor_p.x + minor_dim.x);
|
||
|
combined_dim.y = Max(major_p.y + major_dim.y, minor_p.y + minor_dim.y);
|
||
|
|
||
|
v2 combined_half_dim = V2Mul(combined_dim, 0.5f);
|
||
|
v2 center = RectGetCenter(rect);
|
||
|
|
||
|
major_p = V2Sub(center, combined_half_dim);
|
||
|
minor_p = V2Add(major_p, minor_p);
|
||
|
|
||
|
major_p.x = f32Floor(major_p.x);
|
||
|
major_p.y = f32Floor(major_p.y);
|
||
|
minor_p.x = f32Floor(minor_p.x);
|
||
|
minor_p.y = f32Floor(minor_p.y);
|
||
|
|
||
|
R_String(major_p, major_scale, on_screen_major, text_color, 1.f);
|
||
|
R_String(minor_p, minor_scale, on_screen_minor, text_color, 1.f);
|
||
|
|
||
|
|
||
|
|
||
|
_UI_ButtonPost(ctx);
|
||
|
}
|
||
|
|
||
|
if (ctx->enable_drop_down && !did_button){
|
||
|
if (UI_IdEq(APP_OwnerOfFloatingWindow(), ctx->id)){
|
||
|
b32 drop_result = 0;
|
||
|
UI_ButtonRecord *record = _UI_DropDownButtonSaveRecord(ctx, rect, &drop_result);
|
||
|
if (drop_result){
|
||
|
result = 1;
|
||
|
}
|
||
|
record->kind = UI_ButtonKind_Icon;
|
||
|
record->major = major;
|
||
|
record->minor = minor;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ctx->button_id.v1 += 1;
|
||
|
_UI_ButtonEatExtendedParameters(ctx);
|
||
|
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
internal b32
|
||
|
UI_ButtonLabel(UI_ButtonCtx *ctx, String8 string){
|
||
|
b32 result = 0;
|
||
|
|
||
|
_UI_ButtonHotkey(ctx, &result);
|
||
|
|
||
|
b32 did_button = ctx->has_room;
|
||
|
Rect rect = {0};
|
||
|
if (ctx->has_room){
|
||
|
v2 dim = R_StringDim(ctx->text_scale, string);
|
||
|
if (ctx->enable_flexible_x_advance){
|
||
|
ctx->this_button_x_advance = dim.x + ctx->outline_t*2.f;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
b32 click_result = 0;
|
||
|
UI_ActionLevel action_level;
|
||
|
rect = _UI_ButtonPre(ctx, &action_level, &click_result);
|
||
|
b32 did_tool_tip = ctx->did_tool_tip;
|
||
|
|
||
|
|
||
|
v3 text_color = ctx->cl.front[action_level];
|
||
|
if (!ctx->condition){
|
||
|
text_color = UI_Grayified(text_color);
|
||
|
}
|
||
|
|
||
|
String8 on_screen_string = string;
|
||
|
if (ctx->do_drop_down){
|
||
|
_UI_DropDownButtonClick(ctx, click_result);
|
||
|
on_screen_string = S8Lit("* more *");
|
||
|
text_color = cl_button_text;
|
||
|
did_button = 0;
|
||
|
}
|
||
|
else{
|
||
|
if (click_result){
|
||
|
result = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Rect inner = RectShrink(rect, ctx->outline_t);
|
||
|
|
||
|
v2 p = {0};
|
||
|
p.x = inner.x0;
|
||
|
|
||
|
// vertically align center of text with center of box
|
||
|
// solve for y in: y + 0.5*h = center.y
|
||
|
p.y = RectGetCenter(inner).y - 0.5f*dim.y;
|
||
|
|
||
|
R_StringCapped(p, inner.x1, ctx->text_scale, on_screen_string, text_color, 1.f);
|
||
|
|
||
|
if ((action_level == UI_ActionLevel_Hover ||
|
||
|
action_level == UI_ActionLevel_Flash) &&
|
||
|
did_button && !did_tool_tip &&
|
||
|
dim.x > (inner.x1 - inner.x0)){
|
||
|
APP_SetToolTip(on_screen_string);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
_UI_ButtonPost(ctx);
|
||
|
}
|
||
|
|
||
|
if (ctx->enable_drop_down && !did_button){
|
||
|
if (UI_IdEq(APP_OwnerOfFloatingWindow(), ctx->id)){
|
||
|
b32 drop_result = 0;
|
||
|
UI_ButtonRecord *record = _UI_DropDownButtonSaveRecord(ctx, rect, &drop_result);
|
||
|
if (drop_result){
|
||
|
result = 1;
|
||
|
}
|
||
|
record->kind = UI_ButtonKind_Label;
|
||
|
record->string = string;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ctx->button_id.v1 += 1;
|
||
|
_UI_ButtonEatExtendedParameters(ctx);
|
||
|
|
||
|
return(result);
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
UI_ButtonDropdownCallback(void *ptr, APP_FloatingWindowResult *result){
|
||
|
UI_ButtonDropdown *dropdown = (UI_ButtonDropdown*)ptr;
|
||
|
|
||
|
// NOTE(allen): Select drop down placement
|
||
|
f32 best_area = NegInf32();
|
||
|
v2 best_p = {0};
|
||
|
v2 best_dim = {0};
|
||
|
Side best_cast_dir[2] = {0};
|
||
|
|
||
|
Rect win = MakeRect(0, 0, V2Expand(vars->window_dim));
|
||
|
Rect src = dropdown->source_rect;
|
||
|
|
||
|
for (u32 x_it = 0; x_it < 2; x_it += 1){
|
||
|
u32 x_side = x_it^1;
|
||
|
for (u32 y_side = 0; y_side < 2; y_side += 1){
|
||
|
v2 p = v2(src.p[x_side].x, src.p[y_side].y);
|
||
|
Range x = MakeRange(p.x, win.p[x_side].x);
|
||
|
Range y = MakeRange(p.y, win.p[y_side^1].y);
|
||
|
v2 dim = v2(RangeSize(x), RangeSize(y));
|
||
|
f32 area = dim.x*dim.y;
|
||
|
if (area > best_area){
|
||
|
best_area = area;
|
||
|
best_p = p;
|
||
|
best_dim = dim;
|
||
|
best_cast_dir[Dimension_X] = x_side;
|
||
|
best_cast_dir[Dimension_Y] = y_side^1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// NOTE(allen): Fit the drop down to a nice aesthetic rectangle
|
||
|
v2 weight = V2Hadamard(v2(1.f, SmallGolden32), dropdown->btn_dim);
|
||
|
v2 dim = v2(1, 1);
|
||
|
f32 count = (f32)dropdown->count;
|
||
|
for (;dim.x*dim.y < count;){
|
||
|
f32 scores[2];
|
||
|
for (u32 i = 0; i < 2; i += 1){
|
||
|
dim.v[i] += 1;
|
||
|
scores[i] = V2Dot(dim, weight)/(dim.x*dim.y);
|
||
|
dim.v[i] -= 1;
|
||
|
}
|
||
|
if (scores[0] <= scores[1]){
|
||
|
dim.v[0] += 1;
|
||
|
}
|
||
|
else{
|
||
|
dim.v[1] += 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
v2 pixel_dim = V2Hadamard(dim, dropdown->btn_dim);
|
||
|
|
||
|
Range x = MakeRange(best_p.x, best_p.x + SignOfSide(best_cast_dir[Dimension_X])*pixel_dim.x);
|
||
|
Range y = MakeRange(best_p.y, best_p.y + SignOfSide(best_cast_dir[Dimension_Y])*pixel_dim.y);
|
||
|
Rect rect = MakeRectRanges(x, y);
|
||
|
Rect outer = RectGrow(rect, 4.f);
|
||
|
Rect inner = RectShrink(outer, 2.f);
|
||
|
|
||
|
|
||
|
// NOTE(allen): Place buttons
|
||
|
R_RectOutline(outer, 2.f, cl_white, 1.f);
|
||
|
R_Rect(inner, cl_black, 1.f);
|
||
|
|
||
|
inner.p0 = rect.p0;
|
||
|
|
||
|
UI_ButtonCtx btn_ctx = UI_InitButtonCtx(inner, dropdown->btn_dim, dropdown->font, UI_IdV(0));
|
||
|
|
||
|
for (UI_ButtonRecord *node = dropdown->first;
|
||
|
node != 0;
|
||
|
node = node->next){
|
||
|
MemoryCopyStruct(&btn_ctx.cl, &node->cl);
|
||
|
btn_ctx.outline_t = node->outline_t;
|
||
|
btn_ctx.text_scale = node->text_scale;
|
||
|
UI_NextCondition(&btn_ctx, node->condition);
|
||
|
if (node->has_hot_key){
|
||
|
UI_NextHotkey(&btn_ctx, node->hot_key, node->hot_key_mods);
|
||
|
}
|
||
|
UI_NextTooltip(&btn_ctx, node->tool_tip);
|
||
|
if (node->kind == UI_ButtonKind_Icon){
|
||
|
if (UI_Button(&btn_ctx, node->major, node->minor)){
|
||
|
dropdown->activated = node;
|
||
|
}
|
||
|
}
|
||
|
else if (node->kind == UI_ButtonKind_Label){
|
||
|
if (UI_ButtonLabel(&btn_ctx, node->string)){
|
||
|
dropdown->activated = node;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
result->rect = outer;
|
||
|
}
|