vn/code/vn_ui.cpp

1011 lines
22 KiB
C++
Raw Permalink Normal View History

2023-07-19 15:09:41 +00:00
#include "generated/vn_ui.meta.c"
2023-06-19 17:12:26 +00:00
2023-09-13 04:42:11 +00:00
static ui_size UI_MakeSize(ui_size_kind Kind, r32 Value, r32 Strictness)
2023-06-19 17:12:26 +00:00
{
2023-12-23 07:27:22 +00:00
ui_size Result;
Result.Kind = Kind;
Result.Value = Value;
Result.Strictness = Strictness;
return(Result);
2023-06-19 17:12:26 +00:00
}
2023-06-17 17:00:55 +00:00
per_thread ui *ThreadLocal_UI;
inline void UI_SetState(ui *UI)
{
2023-12-23 07:27:22 +00:00
ThreadLocal_UI = UI;
2023-06-17 17:00:55 +00:00
}
inline ui *UI_GetState(void)
{
2023-12-23 07:27:22 +00:00
return(ThreadLocal_UI);
2023-06-17 17:00:55 +00:00
}
2023-09-13 04:42:11 +00:00
inline ui_key UI_HotKey(void)
2023-06-17 17:00:55 +00:00
{
2023-12-23 07:27:22 +00:00
return(UI_GetState()->Hot);
2023-06-17 17:00:55 +00:00
}
2023-09-13 04:42:11 +00:00
inline ui_key UI_ActiveKey(void)
2023-06-17 17:00:55 +00:00
{
2023-12-23 07:27:22 +00:00
return(UI_GetState()->Active);
2023-06-17 17:00:55 +00:00
}
2023-07-19 15:09:41 +00:00
inline platform_event_list *UI_EventList(void)
{
2023-12-23 07:27:22 +00:00
return(UI_GetState()->EventList);
2023-07-19 15:09:41 +00:00
}
inline arena *UI_FrameArena(void)
2023-08-22 03:19:51 +00:00
{
2023-12-23 07:27:22 +00:00
return(UI_GetState()->FrameArena);
2023-08-22 03:19:51 +00:00
}
inline v2_r32 UI_MouseP(void)
2023-07-19 15:09:41 +00:00
{
2023-12-23 07:27:22 +00:00
return(UI_GetState()->MouseP);
2023-07-19 15:09:41 +00:00
}
inline glyph_atlas *UI_GlyphAtlas(void)
{
2023-12-23 07:27:22 +00:00
return(UI_GetState()->GlyphAtlas);
2023-07-19 15:09:41 +00:00
}
2024-01-20 11:18:57 +00:00
inline void UI_SetDragStartP(v2_r32 P)
2023-06-17 17:00:55 +00:00
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
UI->DragStartP = P;
2023-06-17 17:00:55 +00:00
}
inline void UI_UpdateDragStartP(void)
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
UI->DragStartP = UI->MouseP;
2023-06-17 17:00:55 +00:00
}
2024-01-20 11:18:57 +00:00
inline v2_r32 UI_GetDragStartP(void)
2023-06-17 17:00:55 +00:00
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
return(UI->DragStartP);
2023-06-17 17:00:55 +00:00
}
2024-01-20 11:18:57 +00:00
inline void UI_StoreDragV2(v2_r32 DragData)
2023-06-17 17:00:55 +00:00
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
2024-01-20 11:18:57 +00:00
*(v2_r32 *)&UI->DragData = DragData;
2023-06-17 17:00:55 +00:00
}
2024-01-20 11:18:57 +00:00
inline v2_r32 UI_GetDragV2(void)
2023-06-17 17:00:55 +00:00
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
2024-01-20 11:18:57 +00:00
v2_r32 Result = *(v2_r32 *)UI->DragData;
2023-12-23 07:27:22 +00:00
return(Result);
2023-06-17 17:00:55 +00:00
}
inline void UI_StoreDragR32(r32 DragData)
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
*(r32 *)&UI->DragData = DragData;
2023-06-17 17:00:55 +00:00
}
inline r32 UI_GetDragR32(void)
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
r32 Result = *(r32 *)UI->DragData;
return(Result);
2023-06-17 17:00:55 +00:00
}
inline void UI_StoreDragPayload(void *Source)
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
Copy(UI->DragData, Source, sizeof(UI->DragData));
2023-06-17 17:00:55 +00:00
}
inline void UI_GetDragPayload(void *Dest)
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
Copy(Dest, UI->DragData, sizeof(UI->DragData));
2023-06-17 17:00:55 +00:00
}
inline void UI_StoreDragPointer(void *Data)
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
*(void **)&UI->DragData = Data;
2023-06-17 17:00:55 +00:00
}
inline void *UI_GetDragDataPointer(void)
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
void *Result = *(void **)UI->DragData;
return(Result);
2023-06-17 17:00:55 +00:00
}
static r64 UI_Time(void)
{
2023-12-23 07:27:22 +00:00
return(UI_GetState()->Time);
}
static r64 UI_Blink(void)
{
2024-01-20 11:18:57 +00:00
return(Cos(UI_GetState()->BlinkTime*6.0f)*0.5f+0.5f);
2023-12-23 07:27:22 +00:00
}
static void UI_ResetBlink(void)
{
UI_GetState()->BlinkTime = 0;
}
2023-06-17 17:00:55 +00:00
inline ui_key UI_EmptyKey(void)
{
2023-12-23 07:27:22 +00:00
ui_key Key = {};
return(Key);
2023-06-17 17:00:55 +00:00
}
inline ui_key UI_SeedKey(ui_key Key, ui_key Seed)
{
2023-12-23 07:27:22 +00:00
ui_key Result = {((Key.Value + Seed.Value) << 5) + Key.Value};
return(Result);
2023-06-17 17:00:55 +00:00
}
2023-09-13 04:42:11 +00:00
inline ui_key UI_KeyFromString(string String)
2023-06-17 17:00:55 +00:00
{
2023-12-23 07:27:22 +00:00
ui_key Key;
if(String.Count)
{
Key.Value = HashString(String);
}
else
{
Key = UI_EmptyKey();
}
return(Key);
2023-06-17 17:00:55 +00:00
}
2023-09-13 04:42:11 +00:00
static string UI_BoxStringFromKey(ui_key Key)
2023-06-17 17:00:55 +00:00
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
string Result = StrLit("Empty or Not Found");
if(!AreEqual(Key, UI_EmptyKey()))
{
for(s32 BucketIndex = 0;
2024-01-20 11:18:57 +00:00
BucketIndex < ArrayCount(UI->BoxBuckets);
++BucketIndex)
2023-12-23 07:27:22 +00:00
{
ui_box_bucket *Bucket = UI->BoxBuckets + BucketIndex;
for(ui_box *Box = Bucket->First;
2024-01-20 11:18:57 +00:00
Box != 0;
Box = Box->HashNext)
2023-12-23 07:27:22 +00:00
{
if(AreEqual(Key, Box->Key))
{
Result = Box->String;
goto FoundName;
}
}
}
}
FoundName:
return(Result);
2023-06-17 17:00:55 +00:00
}
2023-09-13 04:42:11 +00:00
inline ui_box *UI_BoxFromKey(ui *UI, ui_key Key)
2023-06-17 17:00:55 +00:00
{
2023-12-23 07:27:22 +00:00
u64 Hash = Key.Value;
u64 Slot = Hash % ArrayCount(UI->BoxBuckets);
ui_box_bucket *Bucket = UI->BoxBuckets + Slot;
ui_box *Result = 0;
if(!AreEqual(Key, UI_EmptyKey()))
{
for(ui_box *Box = Bucket->First;
2024-01-20 11:18:57 +00:00
Box != 0;
Box = Box->HashNext)
2023-12-23 07:27:22 +00:00
{
if(AreEqual(Box->Key, Key))
{
Result = Box;
break;
}
}
}
if(!Result)
{
// sixten: Check if we have already allocated a box
if(DLLIsEmpty(UI->FirstFreeBox))
{
// sixten: If not, simply allocate one
Result = PushStruct(UI->Arena, ui_box);
}
else
{
// sixten: If there exists an already allocated, remove it from the free list and use it.
Result = UI->FirstFreeBox;
DLLRemove_NP(UI->FirstFreeBox, UI->LastFreeBox, Result, HashNext, HashPrev);
}
// sixten: Insert the box into the hashmap.
DLLInsertLast_NP(Bucket->First, Bucket->Last, Result, HashNext, HashPrev);
}
Result->Key = Key;
return(Result);
2023-06-17 17:00:55 +00:00
}
inline ui_box *UI_MakeBox(ui_box_flags Flags, string String)
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
ui_box *Parent = UI_TopParent();
string KeyString = String;
s64 HashIndex = LastIndexOf(KeyString, StrLit("##"));
if(HashIndex != -1)
{
KeyString = Suffix(KeyString, KeyString.Count-HashIndex-2);
}
ui_key BaseKey = UI_KeyFromString(KeyString);
ui_key Seed = UI_SeedKey(BaseKey, Parent ? Parent->Seed : UI_EmptyKey());
ui_key Key = BaseKey.Value ? Seed : UI_EmptyKey();
// sixten: Check for duplicate keys.
2023-06-17 17:00:55 +00:00
#if VN_SLOW
2023-12-23 07:27:22 +00:00
if(Parent && !AreEqual(Key, UI_EmptyKey()))
{
for(ui_box *Child = Parent->First;
2024-01-20 11:18:57 +00:00
Child != 0;
Child = Child->Next)
2023-12-23 07:27:22 +00:00
{
Assert(!AreEqual(Child->Key, Key));
}
}
2023-06-17 17:00:55 +00:00
#endif
2023-12-23 07:27:22 +00:00
ui_box *Box = UI_BoxFromKey(UI, Key);
Box->Seed = Seed;
Box->First = Box->Last = Box->Next = Box->Prev = Box->Parent = 0;
2024-01-20 11:18:57 +00:00
Box->ComputedRelativeP = V2R32(0, 0);
Box->ComputedDim = V2R32(0, 0);
2023-12-23 07:27:22 +00:00
Box->LastFrameTouched = UI->CurrentFrame;
Box->Flags = Flags;
Box->String = PushString(UI->FrameArena, String);
if(HashIndex != -1)
{
Box->String = Prefix(Box->String, HashIndex);
}
else
{
HashIndex = LastIndexOf(Box->String, '#');
if(HashIndex != -1)
{
Box->String = Prefix(Box->String, HashIndex);
}
}
UI_ApplyStyles(Box);
if(Parent)
{
DLLInsertLast(Parent->First, Parent->Last, Box);
}
return(Box);
2023-06-17 17:00:55 +00:00
}
inline ui_box *UI_MakeBoxF(ui_box_flags Flags, char *Format, ...)
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
// sixten(TODO): Allocate on scratch, copy to frame arena
// (alternatively keep two versions of UI_MakeBox, to make sure everything works)
va_list Arguments;
va_start(Arguments, Format);
string String = PushFormatVariadic(UI->FrameArena, Format, Arguments);
va_end(Arguments);
ui_box *Box = UI_MakeBox(Flags, String);
return(Box);
2023-06-17 17:00:55 +00:00
}
inline void UI_SetNextHot(ui_key Key)
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
if(!UI->NextHotSet)
{
UI->NextHot = Key;
UI->NextHotSet = true;
}
2023-06-17 17:00:55 +00:00
}
2023-08-22 03:19:51 +00:00
static void UI_EquipBoxText(ui_box *Box, string String)
{
2023-12-23 07:27:22 +00:00
Box->String = PushString(UI_FrameArena(), String);
2023-08-22 03:19:51 +00:00
}
static void UI_EquipBoxCustomDrawCallback(ui_box *Box, ui_custom_draw_callback *DrawCallback, void *Data)
{
2023-12-23 07:27:22 +00:00
Box->DrawCallback = DrawCallback;
Box->DrawCallbackData = Data;
2023-08-22 03:19:51 +00:00
}
2023-06-27 14:14:28 +00:00
// sixten(NOTE): ClippingRect = Intersection(TrueClippingRect, Parent->Rect);
2024-01-20 11:18:57 +00:00
static b32 UI_ChildrenContainsP(ui_box *Parent, v2_r32 P, range2_r32 Clip)
2023-06-17 17:00:55 +00:00
{
2023-12-23 07:27:22 +00:00
b32 Result = false;
for(ui_box *Child = Parent->First;
2024-01-20 11:18:57 +00:00
Child != 0;
Child = Child->Next)
2023-12-23 07:27:22 +00:00
{
range2_r32 IntersectionRect = Intersection(Clip, Child->Rect);
if(Child->Flags & UI_BoxFlag_Clickable && InRange(IntersectionRect, P))
{
Result = true;
break;
}
else
{
if(Child->Flags & UI_BoxFlag_Clip)
{
Result = UI_ChildrenContainsP(Child, P, IntersectionRect);
}
else
{
Result = UI_ChildrenContainsP(Child, P, Clip);
}
if(Result)
{
break;
}
}
if(Child == Parent->Last)
{
break;
}
}
return(Result);
2023-06-17 17:00:55 +00:00
}
static ui_signal UI_SignalFromBox(ui_box *Box)
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
ui_signal Signal = {};
// sixten: Gather the clipping rects of all the parents.
range2_r32 ClippedRect = Box->Rect;
for(ui_box *Parent = Box->Parent;
2024-01-20 11:18:57 +00:00
Parent != 0;
Parent = Parent->Parent)
2023-12-23 07:27:22 +00:00
{
if(Parent->Flags & UI_BoxFlag_Clip)
{
ClippedRect = Intersection(ClippedRect, Parent->Rect);
}
}
Signal.Hovering = InRange(ClippedRect, UI->MouseP);
// sixten: Make sure no children boxes overlap.
Signal.Hovering &= !UI_ChildrenContainsP(Box, UI->MouseP, ClippedRect);
// sixten: Make sure no previous boxes overlap.
if(Box->Prev)
{
ui_box PrevBoxParent = {};
PrevBoxParent.Rect = Box->Rect;
PrevBoxParent.First = Box->Parent->First;
PrevBoxParent.Last = Box->Prev;
Signal.Hovering &= !UI_ChildrenContainsP(&PrevBoxParent, UI->MouseP, ClippedRect);
}
// sixten: Make sure the tooltip is not overlapping.
{
// sixten: Are we the tooltip?
b32 FoundTooltip = false;
for(ui_box *Parent = Box->Parent;
2024-01-20 11:18:57 +00:00
Parent != 0;
Parent = Parent->Parent)
2023-12-23 07:27:22 +00:00
{
if(Parent == UI->TooltipNode)
{
FoundTooltip = true;
break;
}
}
if(!FoundTooltip)
{
Signal.Hovering &= ~UI_ChildrenContainsP(UI->TooltipNode, UI->MouseP,
2024-01-20 11:18:57 +00:00
Range2R32(V2R32(0, 0), V2R32(InfinityR32, InfinityR32)));
2023-12-23 07:27:22 +00:00
}
}
if(Box->Flags & UI_BoxFlag_Clickable)
{
if(AreEqual(UI->Active, Box->Key))
{
if(Platform_KeyRelease(UI->EventList, Key_MouseLeft))
{
Signal.Clicked = Signal.Hovering;
Signal.Released = true;
UI->Active = UI_EmptyKey();
if(!Signal.Hovering)
{
UI_SetNextHot(UI_EmptyKey());
}
}
else
{
Signal.Dragging = true;
}
}
else
{
if(AreEqual(UI->Hot, Box->Key))
{
if(Platform_KeyPress(UI->EventList, Key_MouseLeft))
{
UI->Active = Box->Key;
UI->DragStartP = UI->MouseP;
Signal.Dragging = true;
Signal.Pressed = true;
}
else if(!Signal.Hovering)
{
UI_SetNextHot(UI_EmptyKey());
}
if(Platform_KeyPress(UI->EventList, Key_MouseRight))
{
Signal.PressedRight = true;
}
}
else
{
if(AreEqual(UI->Hot, UI_EmptyKey()) && Signal.Hovering)
{
UI_SetNextHot(Box->Key);
}
}
}
}
if(AreEqual(UI->Hot, Box->Key))
{
Platform.SetCursor(Box->HoverCursor);
}
Signal.MouseP = UI->MouseP;
Signal.dMouseP = UI->dMouseP;
Signal.DragDelta = UI->MouseP - UI->DragStartP;
Signal.Box = Box;
return(Signal);
2023-06-17 17:00:55 +00:00
}
static void UI_SolveSizeViolations(ui_box *Box, axis2 Axis)
{
2023-12-23 07:27:22 +00:00
r32 TotalSpace = 0;
r32 FixupBudget = 0;
if(!(Box->Flags & (UI_BoxFlag_OverflowX<<Axis)))
{
for(ui_box *Child = Box->First;
2024-01-20 11:18:57 +00:00
Child != 0;
Child = Child->Next)
2023-12-23 07:27:22 +00:00
{
if(!(Child->Flags & (UI_BoxFlag_FloatingX<<Axis)))
{
if(Axis == Box->LayoutAxis)
{
TotalSpace += Child->ComputedDim.E[Axis];
}
else
{
TotalSpace = Maximum(TotalSpace, Child->ComputedDim.E[Axis]);
}
FixupBudget += Child->ComputedDim.E[Axis] * (1 - Child->SemanticSize[Axis].Strictness);
}
}
r32 Violation = TotalSpace - Box->ComputedDim.E[Axis];
if(Violation > 0 && FixupBudget > 0)
{
for(ui_box *Child = Box->First;
2024-01-20 11:18:57 +00:00
Child != 0;
Child = Child->Next)
2023-12-23 07:27:22 +00:00
{
if(!(Child->Flags & (UI_BoxFlag_FloatingX<<Axis)))
{
r32 ChildFixupBudget = Child->ComputedDim.E[Axis] * (1 - Child->SemanticSize[Axis].Strictness);
r32 ChildFixupSize = 0;
if(Axis == Box->LayoutAxis)
{
ChildFixupSize = ChildFixupBudget * (Violation / FixupBudget);
}
else
{
ChildFixupSize = Child->ComputedDim.E[Axis] - Box->ComputedDim.E[Axis];
}
ChildFixupSize = Clamp(ChildFixupSize, 0, ChildFixupBudget);
Child->ComputedDim.E[Axis] -= ChildFixupSize;
}
}
}
}
if(Axis == Box->LayoutAxis)
{
r32 Position = 0;
for(ui_box *Child = Box->First;
2024-01-20 11:18:57 +00:00
Child;
Child = Child->Next)
2023-12-23 07:27:22 +00:00
{
if((Child->Flags & (UI_BoxFlag_FloatingX<<Axis)))
{
Child->ComputedRelativeP.E[Axis] = Child->FixedP.E[Axis];
}
else
{
Child->ComputedRelativeP.E[Axis] = Position;
Position += Child->ComputedDim.E[Axis];
}
}
}
else
{
for(ui_box *Child = Box->First;
2024-01-20 11:18:57 +00:00
Child;
Child = Child->Next)
2023-12-23 07:27:22 +00:00
{
if((Child->Flags & (UI_BoxFlag_FloatingX<<Axis)))
{
Child->ComputedRelativeP.E[Axis] = Child->FixedP.E[Axis];
}
else
{
Child->ComputedRelativeP.E[Axis] = 0;
}
}
}
2023-06-17 17:00:55 +00:00
}
inline void UI_BeginTooltip(void)
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
UI_PushParent(UI->TooltipNode);
2023-06-17 17:00:55 +00:00
}
inline void UI_EndTooltip(void)
{
2023-12-23 07:27:22 +00:00
UI_PopParent();
2023-06-17 17:00:55 +00:00
}
inline void UI_SetNextTooltip(void)
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
UI_SetNextParent(UI->TooltipNode);
2023-06-17 17:00:55 +00:00
}
#define UI_Tooltip DeferLoop(UI_BeginTooltip(), UI_EndTooltip())
static void UI_DrawBox(ui_box *Box, render_group *Group, glyph_atlas *GlyphAtlas)
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
Box->HotTransition += ((r32)AreEqual(UI->Hot, Box->Key) - Box->HotTransition) * 0.6f;
Box->ActiveTransition += ((r32)AreEqual(UI->Active, Box->Key) - Box->ActiveTransition) * 0.6f;
if(Box->Flags & UI_BoxFlag_DrawBackground)
{
2024-01-20 11:18:57 +00:00
PushQuad(Group, Box->Rect, Box->BackgroundColor,
Box->CornerRadii.x, Box->CornerRadii.y, Box->CornerRadii.z, Box->CornerRadii.w,
0, 0);
2023-12-23 07:27:22 +00:00
}
if(Box->Flags & UI_BoxFlag_HotAnimation)
{
2024-01-20 11:18:57 +00:00
v4_r32 Top = V4R32(1, 1, 1, 0.08F*Box->HotTransition);
v4_r32 Bottom = V4R32(1, 1, 1, 0.0);
2023-12-23 07:27:22 +00:00
2024-01-20 11:18:57 +00:00
PushQuad(Group, Box->Rect, Top, Top, Bottom, Bottom,
Box->CornerRadii.x, Box->CornerRadii.y, Box->CornerRadii.z, Box->CornerRadii.w, 0, 0);
2023-12-23 07:27:22 +00:00
}
if(Box->Flags & UI_BoxFlag_ActiveAnimation)
{
2024-01-20 11:18:57 +00:00
v4_r32 Top = V4R32(0, 0, 0, 0.7F*Box->ActiveTransition);
v4_r32 Bottom = V4R32(0, 0, 0, 0.1F*Box->ActiveTransition);
2023-12-23 07:27:22 +00:00
2024-01-20 11:18:57 +00:00
PushQuad(Group, Box->Rect, Top, Top, Bottom, Bottom,
Box->CornerRadii.x, Box->CornerRadii.y, Box->CornerRadii.z, Box->CornerRadii.w, 0, 0);
2023-12-23 07:27:22 +00:00
}
if(Box->Flags & UI_BoxFlag_DrawText)
{
2024-01-20 11:18:57 +00:00
v2_r32 TextDim = V2R32(CalculateRasterizedTextWidth(GlyphAtlas, Box->Font, Box->FontSize, Box->String),
CalculateRasterizedTextHeight(GlyphAtlas, Box->Font, Box->FontSize, Box->String));
2023-12-23 07:27:22 +00:00
2024-01-20 11:18:57 +00:00
v2_r32 P = Box->Rect.Min + (Box->ComputedDim - TextDim)*0.5;
2023-12-23 07:27:22 +00:00
PushText(Group, GlyphAtlas, Box->Font, P, Box->FontSize, Box->TextColor, Box->String);
}
if(Box->DrawCallback)
{
Box->DrawCallback(Group, GlyphAtlas, Box, Box->DrawCallbackData);
}
2023-06-19 17:12:26 +00:00
#if VN_INTERNAL
2023-12-23 07:27:22 +00:00
if(DEBUG_DebugSettings->RenderUIDebugRects)
{
// sixten: Render debug rects around boxes.
r32 R = (((Box->Key.Value >> 0) & ((1 << 22) - 1)) / (r32)((1 << 22) - 1));
r32 G = (((Box->Key.Value >> 21) & ((1 << 22) - 1)) / (r32)((1 << 22) - 1));
r32 B = (((Box->Key.Value >> 42) & ((1 << 22) - 1)) / (r32)((1 << 22) - 1));
2024-01-20 11:18:57 +00:00
v4_r32 Red = V4R32(R, G, B, 1);
2023-12-23 07:27:22 +00:00
PushQuad(Group, Box->Rect, Red, Red, Red, Red, 0, 1.8, 1.8);
}
2023-06-17 17:00:55 +00:00
#endif
2023-12-23 07:27:22 +00:00
for(ui_box *Child = Box->First;
2024-01-20 11:18:57 +00:00
Child != 0;
Child = Child->Next)
2023-12-23 07:27:22 +00:00
{
if(Child->Flags & UI_BoxFlag_DrawDropShadow)
{
r32 ShadowRadius = 10;
range2_r32 Rect = Range2R32(Child->Rect.Min - V2R32(ShadowRadius, ShadowRadius),
2024-01-20 11:18:57 +00:00
Child->Rect.Max + V2R32(ShadowRadius, ShadowRadius));
2023-12-23 07:27:22 +00:00
2024-01-20 11:18:57 +00:00
v4_r32 ShadowColor = V4R32(0, 0, 0, 0.3);
2023-12-23 07:27:22 +00:00
PushQuad(Group, Rect, ShadowColor, 0, ShadowRadius, 0);
}
}
if(Box->Flags & UI_BoxFlag_Clip)
{
range2_r32 Rect = Intersection(Group->ClipStack[Group->ClipStackUsed], Box->Rect);
PushClip(Group, Rect);
}
for(ui_box *Child = Box->First;
2024-01-20 11:18:57 +00:00
Child != 0;
Child = Child->Next)
2023-12-23 07:27:22 +00:00
{
UI_DrawBox(Child, Group, GlyphAtlas);
}
if(Box->Flags & UI_BoxFlag_Clip)
{
PopClip(Group);
}
if(Box->Flags & UI_BoxFlag_DrawBorder)
{
2024-01-20 11:18:57 +00:00
v4_r32 BorderColor = Brighten(Box->BorderColor, Box->HotTransition*0.5f+1.0f);
PushQuad(Group, Box->Rect, BorderColor, Box->CornerRadii.x, Box->CornerRadii.y, Box->CornerRadii.z, Box->CornerRadii.w, 0.8, Box->BorderThickness);
2023-12-23 07:27:22 +00:00
}
2023-06-17 17:00:55 +00:00
}
static r32 UI_CalculateChildrenSum(ui_box *Box, axis2 Axis, b32 ForceCalculate);
2023-06-23 12:38:15 +00:00
static r32 UI_CalculateBoxSize(ui_box *Box, axis2 Axis, b32 ForceCalculate = false)
2023-06-23 12:38:15 +00:00
{
2023-12-23 07:27:22 +00:00
r32 Result = 0;
ui *UI = UI_GetState();
ui_box *Parent = Box->Parent;
switch(Box->SemanticSize[Axis].Kind)
{
case UI_SizeKind_Pixels:
{
Result = Box->SemanticSize[Axis].Value;
} break;
case UI_SizeKind_TextContent:
{
glyph_atlas *Atlas = UI->GlyphAtlas;
Result = ((Axis == Axis2_X) ?
2024-01-20 11:18:57 +00:00
CalculateRasterizedTextWidth(Atlas, Box->Font, Box->FontSize, Box->String) :
CalculateRasterizedTextHeight(Atlas, Box->Font, Box->FontSize, Box->String)) +
2023-12-23 07:27:22 +00:00
Box->SemanticSize[Axis].Value;
} break;
case UI_SizeKind_PercentOfParent:
{
if(Parent && Parent->SemanticSize[Axis].Kind != UI_SizeKind_ChildrenSum)
{
r32 Size = Parent->ComputedDim.E[Axis];
// sixten: if the size is zero, try to find it.
if(Size == 0.0)
{
if(ForceCalculate)
{
Size = UI_CalculateBoxSize(Parent, Axis, ForceCalculate);
}
else
{
Size = DimOfRange(Parent->Rect).E[Axis];
}
}
Result = Size*Box->SemanticSize[Axis].Value;
}
else
{
// sixten(NOTE): A box that depends on the size of the parent can not be allowed if the parent depends
// on the children sum.
InvalidCodepath;
}
} break;
case UI_SizeKind_ChildrenSum:
{
Result = UI_CalculateChildrenSum(Box, Axis, ForceCalculate)*Box->SemanticSize[Axis].Value;
} break;
InvalidDefaultCase;
}
return(Result);
2023-06-23 12:38:15 +00:00
}
static v2_r32 UI_CalculateBoxDim(ui_box *Box)
{
2023-12-23 07:27:22 +00:00
v2_r32 Result = V2R32(UI_CalculateBoxSize(Box, Axis2_X, true), UI_CalculateBoxSize(Box, Axis2_Y, true));
return(Result);
}
static r32 UI_CalculateChildrenSum(ui_box *Box, axis2 Axis, b32 ForceCalculate = false)
2023-06-23 12:38:15 +00:00
{
2023-12-23 07:27:22 +00:00
r32 Result = 0;
if(Box->LayoutAxis == Axis)
{
for(ui_box *Child = Box->First;
2024-01-20 11:18:57 +00:00
Child != 0;
Child = Child->Next)
2023-12-23 07:27:22 +00:00
{
Result += UI_CalculateBoxSize(Child, Axis, ForceCalculate);
}
}
else
{
for(ui_box *Child = Box->First;
2024-01-20 11:18:57 +00:00
Child != 0;
Child = Child->Next)
2023-12-23 07:27:22 +00:00
{
Result = Max(Result, UI_CalculateBoxSize(Child, Axis, ForceCalculate));
}
}
return(Result);
2023-06-23 12:38:15 +00:00
}
2023-06-27 14:14:28 +00:00
static void UI_LayoutBox(ui_box *Box)
2023-06-23 12:38:15 +00:00
{
2023-12-23 07:27:22 +00:00
for(ui_box *Child = Box->First;
2024-01-20 11:18:57 +00:00
Child != 0;
Child = Child->Next)
2023-12-23 07:27:22 +00:00
{
Child->ComputedDim.E[Axis2_X] = UI_CalculateBoxSize(Child, Axis2_X);
Child->ComputedDim.E[Axis2_Y] = UI_CalculateBoxSize(Child, Axis2_Y);
}
UI_SolveSizeViolations(Box, Axis2_X);
UI_SolveSizeViolations(Box, Axis2_Y);
for(ui_box *Child = Box->First;
2024-01-20 11:18:57 +00:00
Child != 0;
Child = Child->Next)
2023-12-23 07:27:22 +00:00
{
Child->Rect.Min = Box->Rect.Min + Child->ComputedRelativeP + Box->Offset;
Child->Rect.Max = Child->Rect.Min + Child->ComputedDim;
UI_LayoutBox(Child);
}
2023-06-23 12:38:15 +00:00
}
2023-07-19 15:09:41 +00:00
static void UI_Init(ui *UI)
{
2024-01-20 11:18:57 +00:00
UI->Arena = ArenaAlloc(Kilobytes(4), true, "UI State Arena");
UI->FrameArena = ArenaAlloc(Kilobytes(16), true, "UI Frame Arena");
2023-07-19 15:09:41 +00:00
}
2024-01-20 11:18:57 +00:00
static void UI_BeginBuild(v2_r32 ScreenDim)
2023-06-17 17:00:55 +00:00
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
UI_PushParent(0);
UI_PushWidth(UI_Pixels(ScreenDim.x, 1));
UI_PushHeight(UI_Pixels(ScreenDim.y, 1));
UI_PushFixedX(0);
UI_PushFixedY(0);
UI_PushTextColor(Theme_TextColor);
UI_PushBackgroundColor(ColorFromHex(0x111111FF));
UI_PushBorderColor(Theme_BorderColor);
UI_PushBorderThickness(1.8);
UI_PushLayoutAxis(Axis2_Y);
UI_PushCornerRadius(0);
UI_PushFont(Font_Regular);
UI_PushFontSize(15.0f);
UI_PushOffsetX(0);
UI_PushOffsetY(0);
UI_PushHoverCursor(PlatformCursor_Arrow);
UI->RootNode = UI_MakeBox(UI_BoxFlag_DrawBackground, StrLit("UI Root Node"));
UI->Stacks.ParentStack[0] = UI->RootNode;
UI->ContainerNode = UI_MakeBox(UI_BoxFlag_Clickable, StrLit("UI Container Node"));
UI->TooltipNode = UI_MakeBox(UI_BoxFlag_FloatingX|UI_BoxFlag_FloatingY, StrLit("UI Tooltip Node"));
UI->Stacks.ParentStack[0] = UI->ContainerNode;
UI->NextHotSet = false;
2023-06-17 17:00:55 +00:00
}
2023-06-27 14:14:28 +00:00
static void UI_EndBuild(void)
2023-06-17 17:00:55 +00:00
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
UI_SignalFromBox(UI_GetState()->ContainerNode);
if(UI->NextHotSet)
{
UI->Hot = UI->NextHot;
}
UI_PopParent();
UI_PopWidth();
UI_PopHeight();
UI_PopFixedX();
UI_PopFixedY();
UI_PopTextColor();
UI_PopBackgroundColor();
UI_PopBorderColor();
UI_PopBorderThickness();
UI_PopLayoutAxis();
UI_PopCornerRadius();
UI_PopFont();
UI_PopFontSize();
UI_PopOffsetX();
UI_PopOffsetY();
UI_PopHoverCursor();
UI_LayoutBox(UI->RootNode);
2023-06-17 17:00:55 +00:00
}
static void UI_RenderFrame(render_group *Group, glyph_atlas *GlyphAtlas)
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
UI_DrawBox(UI->RootNode, Group, GlyphAtlas);
2023-06-17 17:00:55 +00:00
}
inline void UI_ScanForHotAndActive(ui_box *Box, b32 *FoundHot, b32 *FoundActive)
{
2023-12-23 07:27:22 +00:00
ui *UI = UI_GetState();
if(AreEqual(UI->Hot, Box->Key) && (Box->Flags & UI_BoxFlag_Clickable))
{
*FoundHot = true;
}
if(AreEqual(UI->Active, Box->Key) && (Box->Flags & UI_BoxFlag_Clickable))
{
*FoundActive = true;
}
if(Box->First)
{
UI_ScanForHotAndActive(Box->First, FoundHot, FoundActive);
}
if(Box->Next)
{
UI_ScanForHotAndActive(Box->Next, FoundHot, FoundActive);
}
2023-06-17 17:00:55 +00:00
}
2024-01-20 11:18:57 +00:00
static void UI_NewFrame(ui *UI, platform_event_list *EventList, v2_r32 MouseP, r32 dtForFrame, glyph_atlas *GlyphAtlas)
2023-06-17 17:00:55 +00:00
{
2023-12-23 07:27:22 +00:00
UI_SetState(UI);
ArenaClear(UI->FrameArena);
UI->EventList = EventList;
UI->dMouseP = MouseP - UI->MouseP;
UI->MouseP = MouseP;
UI->Time += dtForFrame;
UI->BlinkTime += dtForFrame;
UI->GlyphAtlas = GlyphAtlas;
// sixten: Make sure that the hot and active boxes are valid.
if(UI->RootNode)
{
b32 FoundHot = false;
b32 FoundActive = false;
UI_ScanForHotAndActive(UI->RootNode, &FoundHot, &FoundActive);
// sixten(TODO): Do we inform the builder code about this somehow?
if(!FoundHot)
{
UI->Hot = UI_EmptyKey();
}
if(!FoundActive)
{
UI->Active = UI_EmptyKey();
}
}
// sixten: Prune uncached boxes.
ui_box_bucket *FirstBucket = UI->BoxBuckets;
for(ui_box *Box = FirstBucket->First; Box != 0;)
{
if(AreEqual(Box->Key, UI_EmptyKey()))
{
ui_box *ToRemove = Box;
Box = Box->HashNext;
DLLRemove_NP(FirstBucket->First, FirstBucket->Last, ToRemove, HashNext, HashPrev);
*ToRemove = {};
DLLInsertLast_NP(UI->FirstFreeBox, UI->LastFreeBox, ToRemove, HashNext, HashPrev);
}
else
{
Box = Box->HashNext;
}
}
// sixten: Prune any unused boxes.
for(s32 BucketIndex = 0;
2024-01-20 11:18:57 +00:00
BucketIndex < ArrayCount(UI->BoxBuckets);
++BucketIndex)
2023-12-23 07:27:22 +00:00
{
ui_box_bucket *Bucket = UI->BoxBuckets + BucketIndex;
for(ui_box *Box = Bucket->First; Box != 0;)
{
if(Box->LastFrameTouched != UI->CurrentFrame)
{
ui_box *ToRemove = Box;
Box = Box->HashNext;
DLLRemove_NP(Bucket->First, Bucket->Last, ToRemove, HashNext, HashPrev);
*ToRemove = {};
DLLInsertLast_NP(UI->FirstFreeBox, UI->LastFreeBox, ToRemove, HashNext, HashPrev);
}
else
{
Box = Box->HashNext;
}
}
}
++UI->CurrentFrame;
2023-06-17 17:00:55 +00:00
}