1463 lines
54 KiB
C
1463 lines
54 KiB
C
UI_State UI_state = ZI;
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Bootstrap
|
|
|
|
void UI_Bootstrap(void)
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Font helpers
|
|
|
|
GC_FontKey UI_GetDefaultFont(void)
|
|
{
|
|
return GC_FontKeyFromResource(ResourceKeyFromStore(&UI_Resources, Lit("font/proggy.ttf")));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Key helpers
|
|
|
|
UI_Key UI_KeyFromString(String str)
|
|
{
|
|
u64 top_tag = UI_UseTop(Tag);
|
|
UI_Key key = ZI;
|
|
key.hash = HashFnv64(top_tag, str);
|
|
return key;
|
|
}
|
|
|
|
UI_Key UI_KeyF_(String fmt, ...)
|
|
{
|
|
TempArena scratch = BeginScratchNoConflict();
|
|
UI_Key key = ZI;
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
{
|
|
String name = FormatStringV(scratch.arena, fmt, args);
|
|
key = UI_KeyFromString(name);
|
|
}
|
|
va_end(args);
|
|
EndScratch(scratch);
|
|
return key;
|
|
}
|
|
|
|
UI_Key UI_TransKey(void)
|
|
{
|
|
UI_Frame *frame = UI_CurrentFrame();
|
|
u64 seed = ++frame->transient_key_seed;
|
|
UI_Key key = ZI;
|
|
key.hash = RandU64FromSeed(seed);
|
|
return key;
|
|
}
|
|
|
|
UI_Box *UI_BoxFromKey(UI_Key key)
|
|
{
|
|
UI_State *g = &UI_state;
|
|
UI_Box *box = 0;
|
|
if (key.hash != 0)
|
|
{
|
|
UI_BoxBin *bin = &g->box_bins[key.hash % UI_NumBoxLookupBins];
|
|
for (UI_Box *tmp = bin->first; tmp; tmp = tmp->next_in_bin)
|
|
{
|
|
if (tmp->key.hash == key.hash)
|
|
{
|
|
box = tmp;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return box;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ String helpers
|
|
|
|
String UI_StringF_(String fmt, ...)
|
|
{
|
|
UI_Frame *frame = UI_CurrentFrame();
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
String str = FormatStringV(frame->arena, fmt, args);
|
|
va_end(args);
|
|
return str;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Checkpoint helpers
|
|
|
|
UI_Checkpoint UI_PushCP(UI_Key parent)
|
|
{
|
|
UI_Frame *frame = UI_CurrentFrame();
|
|
UI_Stack *stack = frame->top_stack;
|
|
stack->top_checkpoint.v += 1;
|
|
if (parent.hash != 0)
|
|
{
|
|
UI_Push(Parent, parent);
|
|
}
|
|
return stack->top_checkpoint;
|
|
}
|
|
|
|
void UI_PopCP(UI_Checkpoint cp)
|
|
{
|
|
UI_Frame *frame = UI_CurrentFrame();
|
|
UI_Stack *stack = frame->top_stack;
|
|
for (UI_StyleKind kind = UI_StyleKind_None; kind < UI_StyleKind_Count; ++kind)
|
|
{
|
|
UI_StyleNode *n = stack->style_tops[kind];
|
|
while (n && n->checkpoint.v >= cp.v)
|
|
{
|
|
UI_StyleNode *next = n->next;
|
|
n->next = frame->first_free_style_node;
|
|
frame->first_free_style_node = n;
|
|
stack->style_tops[kind] = next;
|
|
n = next;
|
|
}
|
|
}
|
|
stack->top_checkpoint.v = cp.v - 1;
|
|
}
|
|
|
|
UI_Checkpoint UI_TopCP(void)
|
|
{
|
|
UI_Frame *frame = UI_CurrentFrame();
|
|
UI_Stack *stack = frame->top_stack;
|
|
return stack->top_checkpoint;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Style stack helpers
|
|
|
|
void UI_PushDefaults(void)
|
|
{
|
|
UI_Frame *frame = UI_CurrentFrame();
|
|
UI_Stack *stack = frame->top_stack;
|
|
UI_Checkpoint checkpoint = stack->top_checkpoint;
|
|
{
|
|
for (UI_StyleKind kind = UI_StyleKind_None; kind < UI_StyleKind_Count; ++kind)
|
|
{
|
|
UI_StyleDesc desc = ZI;
|
|
desc.style.kind = kind;
|
|
switch (kind)
|
|
{
|
|
default: break;
|
|
case UI_StyleKind_Parent: { desc.style.Parent = UI_RootKey; } break;
|
|
case UI_StyleKind_Width: { desc.style.Width = UI_GROW(1, 0); } break;
|
|
case UI_StyleKind_Height: { desc.style.Height = UI_GROW(1, 0); }
|
|
case UI_StyleKind_Font: { desc.style.Font = UI_GetDefaultFont(); } break;
|
|
|
|
u8 prefetch[127] = ZI;
|
|
for (u64 i = 0; i < countof(prefetch); ++i)
|
|
{
|
|
prefetch[i] = i;
|
|
}
|
|
case UI_StyleKind_FontSize: { desc.style.FontSize = 16.0f; } break;
|
|
case UI_StyleKind_Tint: { desc.style.Tint = Color_White; } break;
|
|
case UI_StyleKind_Tag: { desc.style.Tag = HashFnv64(Fnv64Basis, Lit("root")); } break;
|
|
case UI_StyleKind_DebugColor: { desc.style.DebugColor = Rgba(1, 0, 1, 0.5); } break;
|
|
case UI_StyleKind_BackgroundTextureUv1: { desc.style.BackgroundTextureUv1 = VEC2(1, 1); } break;
|
|
};
|
|
UI_PushStyle(desc);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UI_PushStyle(UI_StyleDesc desc)
|
|
{
|
|
UI_Frame *frame = UI_CurrentFrame();
|
|
UI_Stack *stack = frame->top_stack;
|
|
UI_StyleKind kind = desc.style.kind;
|
|
if (kind >= UI_StyleKind_BeginVirtualStyles_)
|
|
{
|
|
switch(kind)
|
|
{
|
|
default: break;
|
|
|
|
case UI_StyleKind_AxisSize:
|
|
{
|
|
if (desc.axis == Axis_X)
|
|
{
|
|
UI_PushCopy(Width, desc, desc.style.AxisSize);
|
|
}
|
|
else
|
|
{
|
|
UI_PushCopy(Height, desc, desc.style.AxisSize);
|
|
}
|
|
} break;
|
|
|
|
case UI_StyleKind_ChildAlignment:
|
|
{
|
|
UI_Alignment alignment = desc.style.ChildAlignment;
|
|
/* Alignment -> horizontal alignment */
|
|
switch(alignment)
|
|
{
|
|
default: break;
|
|
case UI_Alignment_TopLeft:
|
|
case UI_Alignment_Left:
|
|
case UI_Alignment_BottomLeft:
|
|
{
|
|
UI_PushCopy(ChildAlignmentX, desc, UI_AxisAlignment_Start);
|
|
} break;
|
|
case UI_Alignment_Top:
|
|
case UI_Alignment_Center:
|
|
case UI_Alignment_Bottom:
|
|
{
|
|
UI_PushCopy(ChildAlignmentX, desc, UI_AxisAlignment_Center);
|
|
} break;
|
|
case UI_Alignment_TopRight:
|
|
case UI_Alignment_Right:
|
|
case UI_Alignment_BottomRight:
|
|
{
|
|
UI_PushCopy(ChildAlignmentX, desc, UI_AxisAlignment_End);
|
|
} break;
|
|
}
|
|
/* Alignment -> vertical alignment */
|
|
switch(alignment)
|
|
{
|
|
default: break;
|
|
case UI_Alignment_TopLeft:
|
|
case UI_Alignment_Top:
|
|
case UI_Alignment_TopRight:
|
|
{
|
|
UI_PushCopy(ChildAlignmentY, desc, UI_AxisAlignment_Start);
|
|
} break;
|
|
case UI_Alignment_Left:
|
|
case UI_Alignment_Center:
|
|
case UI_Alignment_Right:
|
|
{
|
|
UI_PushCopy(ChildAlignmentY, desc, UI_AxisAlignment_Center);
|
|
} break;
|
|
case UI_Alignment_BottomLeft:
|
|
case UI_Alignment_Bottom:
|
|
case UI_Alignment_BottomRight:
|
|
{
|
|
UI_PushCopy(ChildAlignmentY, desc, UI_AxisAlignment_End);
|
|
} break;
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UI_StyleNode *n = stack->style_tops[kind];
|
|
if (!(n && n->override))
|
|
{
|
|
{
|
|
if (n && n->pop_when_used)
|
|
{
|
|
UI_StyleNode *next = n->next;
|
|
ZeroStruct(n);
|
|
n->next = next;
|
|
}
|
|
else
|
|
{
|
|
n = frame->first_free_style_node;
|
|
if (n)
|
|
{
|
|
frame->first_free_style_node = n->next;
|
|
ZeroStruct(n);
|
|
}
|
|
else
|
|
{
|
|
n = PushStruct(frame->arena, UI_StyleNode);
|
|
}
|
|
n->next = stack->style_tops[kind];
|
|
stack->style_tops[kind] = n;
|
|
}
|
|
n->style = desc.style;
|
|
n->pop_when_used = desc.pop_when_used;
|
|
n->override = desc.override;
|
|
n->checkpoint = stack->top_checkpoint;
|
|
}
|
|
|
|
/* Initialize style data from desc */
|
|
switch (kind)
|
|
{
|
|
default: break;
|
|
|
|
case UI_StyleKind_Text:
|
|
{
|
|
n->style.Text = PushString(frame->arena, desc.style.Text);
|
|
} break;
|
|
|
|
case UI_StyleKind_Tag:
|
|
{
|
|
if (n->next != 0)
|
|
{
|
|
n->style.Tag = RandU64FromSeeds(n->next->style.Tag, n->style.Tag);
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UI_Style UI_PopStyle(UI_StyleDesc desc)
|
|
{
|
|
UI_Frame *frame = UI_CurrentFrame();
|
|
UI_Stack *stack = frame->top_stack;
|
|
UI_Style result = ZI;
|
|
UI_StyleKind kind = desc.style.kind;
|
|
result.kind = kind;
|
|
if (kind >= UI_StyleKind_BeginVirtualStyles_)
|
|
{
|
|
switch(kind)
|
|
{
|
|
default: break;
|
|
|
|
case UI_StyleKind_AxisSize:
|
|
{
|
|
if (desc.axis == Axis_X)
|
|
{
|
|
desc.style.kind = UI_StyleKind_Width;
|
|
result = UI_PopStyle(desc);
|
|
}
|
|
else
|
|
{
|
|
desc.style.kind = UI_StyleKind_Height;
|
|
result = UI_PopStyle(desc);
|
|
}
|
|
};
|
|
|
|
case UI_StyleKind_ChildAlignment:
|
|
{
|
|
UI_StyleDesc x_desc = desc;
|
|
UI_StyleDesc y_desc = desc;
|
|
x_desc.style.kind = UI_StyleKind_ChildAlignmentX;
|
|
y_desc.style.kind = UI_StyleKind_ChildAlignmentY;
|
|
UI_AxisAlignment x_alignment = UI_PopStyle(x_desc).ChildAlignmentX;
|
|
UI_AxisAlignment y_alignment = UI_PopStyle(y_desc).ChildAlignmentY;
|
|
switch(y_alignment)
|
|
{
|
|
case UI_AxisAlignment_Start:
|
|
{
|
|
switch(x_alignment)
|
|
{
|
|
case UI_AxisAlignment_Start: { result.ChildAlignment = UI_Alignment_TopLeft; } break;
|
|
case UI_AxisAlignment_Center: { result.ChildAlignment = UI_Alignment_Top; } break;
|
|
case UI_AxisAlignment_End: { result.ChildAlignment = UI_Alignment_TopRight; } break;
|
|
}
|
|
} break;
|
|
case UI_AxisAlignment_Center:
|
|
{
|
|
switch(x_alignment)
|
|
{
|
|
case UI_AxisAlignment_Start: { result.ChildAlignment = UI_Alignment_Left; } break;
|
|
case UI_AxisAlignment_Center: { result.ChildAlignment = UI_Alignment_Center; } break;
|
|
case UI_AxisAlignment_End: { result.ChildAlignment = UI_Alignment_Right; } break;
|
|
}
|
|
} break;
|
|
case UI_AxisAlignment_End:
|
|
{
|
|
switch(x_alignment)
|
|
{
|
|
case UI_AxisAlignment_Start: { result.ChildAlignment = UI_Alignment_BottomLeft; } break;
|
|
case UI_AxisAlignment_Center: { result.ChildAlignment = UI_Alignment_Bottom; } break;
|
|
case UI_AxisAlignment_End: { result.ChildAlignment = UI_Alignment_BottomRight; } break;
|
|
}
|
|
} break;
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UI_StyleNode *n = stack->style_tops[kind];
|
|
result = n->style;
|
|
if (desc.use && n->pop_when_used)
|
|
{
|
|
stack->style_tops[kind] = n->next;
|
|
n->next = frame->first_free_style_node;
|
|
frame->first_free_style_node = n;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Command helpers
|
|
|
|
UI_Key UI_BuildBoxEx(UI_Key key)
|
|
{
|
|
UI_Frame *frame = UI_CurrentFrame();
|
|
UI_CmdNode *n = PushStruct(frame->arena, UI_CmdNode);
|
|
n->cmd.kind = UI_CmdKind_BuildBox;
|
|
{
|
|
n->cmd.box.key = key;
|
|
n->cmd.box.parent = UI_UseTop(Parent);
|
|
n->cmd.box.flags = UI_UseTop(Flags);
|
|
n->cmd.box.pref_size[Axis_X] = UI_UseTop(Width);
|
|
n->cmd.box.pref_size[Axis_Y] = UI_UseTop(Height);
|
|
n->cmd.box.child_alignment[Axis_X] = UI_UseTop(ChildAlignmentX);
|
|
n->cmd.box.child_alignment[Axis_Y] = UI_UseTop(ChildAlignmentY);
|
|
n->cmd.box.child_layout_axis = UI_UseTop(ChildLayoutAxis);
|
|
n->cmd.box.background_color = UI_UseTop(BackgroundColor);
|
|
n->cmd.box.border_color = UI_UseTop(BorderColor);
|
|
n->cmd.box.debug_color = UI_UseTop(DebugColor);
|
|
n->cmd.box.tint = UI_UseTop(Tint);
|
|
n->cmd.box.border = UI_UseTop(Border);
|
|
n->cmd.box.font = UI_UseTop(Font);
|
|
n->cmd.box.font_size = UI_UseTop(FontSize);
|
|
n->cmd.box.rounding = UI_UseTop(Rounding);
|
|
n->cmd.box.text = UI_UseTop(Text);
|
|
n->cmd.box.floating_pos = UI_UseTop(FloatingPos);
|
|
}
|
|
++frame->cmds_count;
|
|
SllQueuePush(frame->first_cmd_node, frame->last_cmd_node, n);
|
|
return key;
|
|
}
|
|
|
|
|
|
void UI_SetRawTexture(UI_Key key, G_Texture2DRef tex, Vec2 uv0, Vec2 uv1)
|
|
{
|
|
UI_Frame *frame = UI_CurrentFrame();
|
|
UI_CmdNode *n = PushStruct(frame->arena, UI_CmdNode);
|
|
n->cmd.kind = UI_CmdKind_SetRawTexture;
|
|
{
|
|
n->cmd.set_raw_texture.key = key;
|
|
n->cmd.set_raw_texture.tex = tex;
|
|
n->cmd.set_raw_texture.uv0 = uv0;
|
|
n->cmd.set_raw_texture.uv1 = uv1;
|
|
}
|
|
++frame->cmds_count;
|
|
SllQueuePush(frame->first_cmd_node, frame->last_cmd_node, n);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Report
|
|
|
|
UI_Report UI_ReportFromKey(UI_Key key)
|
|
{
|
|
UI_State *g = &UI_state;
|
|
UI_Report result = ZI;
|
|
|
|
UI_Box *box = UI_BoxFromKey(key);
|
|
if (box)
|
|
{
|
|
result = box->report;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Begin frame
|
|
|
|
UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags, Vec4 swapchain_color)
|
|
{
|
|
UI_State *g = &UI_state;
|
|
|
|
//////////////////////////////
|
|
//- Init persistent state
|
|
|
|
if (!g->box_arena)
|
|
{
|
|
g->box_arena = AcquireArena(Gibi(64));
|
|
g->box_bins = PushStructs(g->box_arena, UI_BoxBin, UI_NumBoxLookupBins);
|
|
|
|
for (u64 i = 0; i < countof(g->frames); ++i)
|
|
{
|
|
UI_Frame *frame = &g->frames[i];
|
|
frame->arena = AcquireArena(Gibi(64));
|
|
frame->rects_arena = AcquireArena(Gibi(64));
|
|
frame->gpu_arena = G_AcquireArena();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////
|
|
//- Begin frame
|
|
|
|
u64 last_frame_idx = g->current_frame_idx;
|
|
u64 frame_idx = last_frame_idx + 1;
|
|
if (frame_idx >= countof(g->frames))
|
|
{
|
|
frame_idx = 0;
|
|
}
|
|
g->current_frame_idx = frame_idx;
|
|
|
|
UI_Frame *last_frame = &g->frames[last_frame_idx];
|
|
UI_Frame *frame = &g->frames[frame_idx];
|
|
|
|
{
|
|
Arena *old_arena = frame->arena;
|
|
Arena *old_rects_arena = frame->arena;
|
|
G_ArenaHandle old_gpu_arena = frame->gpu_arena;
|
|
ZeroStruct(frame);
|
|
frame->arena = old_arena;
|
|
frame->rects_arena = old_rects_arena;
|
|
frame->gpu_arena = old_gpu_arena;
|
|
}
|
|
frame->window_frame = WND_BeginFrame(G_Format_R16G16B16A16_Float, WND_BackbufferSizeMode_MatchMonitor);
|
|
frame->cl = G_PrepareCommandList(G_QueueKind_Direct);
|
|
ResetArena(frame->arena);
|
|
ResetArena(frame->rects_arena);
|
|
G_ResetArena(frame->cl, frame->gpu_arena);
|
|
|
|
{
|
|
i64 now_ns = TimeNs();
|
|
i64 dt_ns = now_ns - last_frame->time_ns;
|
|
frame->time_ns = now_ns;
|
|
frame->dt_ns = dt_ns;
|
|
frame->tick = last_frame->tick + 1;
|
|
}
|
|
|
|
/* Init style stack */
|
|
{
|
|
frame->top_stack = PushStruct(frame->arena, UI_Stack);
|
|
UI_PushDefaults();
|
|
}
|
|
|
|
frame->frame_flags = frame_flags;
|
|
frame->swapchain_color = swapchain_color;
|
|
|
|
//////////////////////////////
|
|
//- Process controller events
|
|
|
|
if (last_frame->boxes_pre != 0)
|
|
{
|
|
ControllerEventsArray controller_events = frame->window_frame.controller_events;
|
|
|
|
frame->cursor_pos = last_frame->cursor_pos;
|
|
f64 dt = SecondsFromNs(frame->dt_ns);
|
|
f64 inv_dt = 1.0 / dt;
|
|
|
|
/* Locate boxes */
|
|
UI_Box *hovered_box = 0;
|
|
UI_Box *active_box = UI_BoxFromKey(last_frame->active_box);
|
|
|
|
/* Update cursor pos */
|
|
for (u64 cev_index = 0; cev_index < controller_events.count; ++cev_index)
|
|
{
|
|
ControllerEvent cev = controller_events.events[cev_index];
|
|
if (cev.kind == ControllerEventKind_CursorMove)
|
|
{
|
|
frame->cursor_pos = Vec2FromVec(cev.cursor_pos);
|
|
}
|
|
}
|
|
|
|
/* Init box reports */
|
|
for (u64 pre_index = g->boxes_count; pre_index-- > 0;)
|
|
{
|
|
UI_Box *box = last_frame->boxes_pre[pre_index];
|
|
if (hovered_box == 0 && box->desc.flags & UI_BoxFlag_Interactable)
|
|
{
|
|
b32 is_cursor_in_box = 0;
|
|
{
|
|
/* TODO: More efficient test. This logic is just copied from the renderer's SDF function for now. */
|
|
Vec2 p0 = box->p0;
|
|
Vec2 p1 = box->p1;
|
|
Vec2 point = frame->cursor_pos;
|
|
b32 is_corner = 0;
|
|
f32 non_corner_edge_dist = MinF32(MinF32(point.x - p0.x, p1.x - point.x), MinF32(point.y - p0.y, p1.y - point.y));
|
|
f32 corner_edge_dist = non_corner_edge_dist;
|
|
if (non_corner_edge_dist >= 0)
|
|
{
|
|
f32 tl_radius = box->rounding_tl;
|
|
f32 tr_radius = box->rounding_tr;
|
|
f32 br_radius = box->rounding_br;
|
|
f32 bl_radius = box->rounding_bl;
|
|
Vec2 tl = VEC2(p0.x + tl_radius, p0.y + tl_radius);
|
|
Vec2 tr = VEC2(p1.x - tr_radius, p0.y + tr_radius);
|
|
Vec2 br = VEC2(p1.x - br_radius, p1.y - br_radius);
|
|
Vec2 bl = VEC2(p0.x + bl_radius, p1.y - bl_radius);
|
|
if (point.x < tl.x && point.y < tl.y) corner_edge_dist = MinF32(corner_edge_dist, tl_radius - Vec2Len(SubVec2(tl, point)));
|
|
if (point.x > tr.x && point.y < tr.y) corner_edge_dist = MinF32(corner_edge_dist, tr_radius - Vec2Len(SubVec2(tr, point)));
|
|
if (point.x > br.x && point.y > br.y) corner_edge_dist = MinF32(corner_edge_dist, br_radius - Vec2Len(SubVec2(br, point)));
|
|
if (point.x < bl.x && point.y > bl.y) corner_edge_dist = MinF32(corner_edge_dist, bl_radius - Vec2Len(SubVec2(bl, point)));
|
|
}
|
|
is_cursor_in_box = non_corner_edge_dist >= 0 && corner_edge_dist >= 0;
|
|
}
|
|
|
|
if (is_cursor_in_box)
|
|
{
|
|
hovered_box = box;
|
|
}
|
|
}
|
|
box->report.m1_ups = 0;
|
|
box->report.m1_downs = 0;
|
|
box->report.m1_presses = 0;
|
|
}
|
|
|
|
/* Update state from controller events */
|
|
for (u64 cev_index = 0; cev_index < controller_events.count; ++cev_index)
|
|
{
|
|
ControllerEvent cev = controller_events.events[cev_index];
|
|
switch (cev.kind)
|
|
{
|
|
default: break;
|
|
|
|
case ControllerEventKind_ButtonDown:
|
|
{
|
|
if (cev.button == Button_M1)
|
|
{
|
|
if (hovered_box)
|
|
{
|
|
++hovered_box->report.m1_downs;
|
|
hovered_box->report.m1_held = 1;
|
|
hovered_box->report.last_m1_offset = SubVec2(frame->cursor_pos, hovered_box->p0);
|
|
active_box = hovered_box;
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case ControllerEventKind_ButtonUp:
|
|
{
|
|
if (cev.button == Button_M1)
|
|
{
|
|
if (active_box)
|
|
{
|
|
if (active_box == hovered_box)
|
|
{
|
|
++active_box->report.m1_presses;
|
|
}
|
|
++active_box->report.m1_ups;
|
|
active_box->report.m1_held = 0;
|
|
active_box = 0;
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case ControllerEventKind_Quit:
|
|
{
|
|
SignalExit(0);
|
|
} break;
|
|
}
|
|
}
|
|
|
|
/* Update box hot & active states */
|
|
for (u64 pre_index = 0; pre_index < g->boxes_count; ++pre_index)
|
|
{
|
|
UI_Box *box = last_frame->boxes_pre[pre_index];
|
|
UI_Report *report = &box->report;
|
|
f32 target_hot = box == active_box || (box == hovered_box && (box == active_box || active_box == 0));
|
|
f32 target_active = box == active_box;
|
|
f32 target_hovered = box == hovered_box;
|
|
f32 hot_blend_rate = target_hot == 1 ? 1 : (20 * dt);
|
|
f32 active_blend_rate = target_active == 1 ? 1 : (20 * dt);
|
|
f32 hovered_blend_rate = target_hovered == 1 ? 1 : (20 * dt);
|
|
report->hot = LerpF32(report->hot, target_hot, hot_blend_rate);
|
|
report->active = LerpF32(report->active, target_active, active_blend_rate);
|
|
report->hovered = LerpF32(report->hovered, target_hovered, hovered_blend_rate);
|
|
|
|
report->screen_p0 = box->p0;
|
|
report->screen_p1 = box->p1;
|
|
}
|
|
|
|
frame->hovered_box = hovered_box ? hovered_box->key : UI_NilKey;
|
|
frame->active_box = active_box ? active_box->key : UI_NilKey;
|
|
}
|
|
|
|
//////////////////////////////
|
|
//- Build root box
|
|
|
|
{
|
|
UI_SetNext(Width, UI_PIX(frame->window_frame.draw_size.x, 1));
|
|
UI_SetNext(Height, UI_PIX(frame->window_frame.draw_size.y, 1));
|
|
UI_SetNext(Parent, UI_NilKey);
|
|
UI_BuildBoxEx(UI_RootKey);
|
|
}
|
|
|
|
return frame;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Frame helpers
|
|
|
|
UI_Frame *UI_CurrentFrame(void)
|
|
{
|
|
UI_State *g = &UI_state;
|
|
return &g->frames[g->current_frame_idx];
|
|
};
|
|
|
|
Arena *UI_FrameArena(void)
|
|
{
|
|
return UI_CurrentFrame()->arena;
|
|
}
|
|
|
|
Vec2 UI_CursorPos(void)
|
|
{
|
|
return UI_CurrentFrame()->cursor_pos;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ End frame
|
|
|
|
void UI_EndFrame(UI_Frame *frame)
|
|
{
|
|
TempArena scratch = BeginScratchNoConflict();
|
|
UI_State *g = &UI_state;
|
|
|
|
Vec2I32 monitor_size = frame->window_frame.monitor_size;
|
|
|
|
Vec2I32 draw_size = frame->window_frame.draw_size;
|
|
Rng3 draw_viewport = RNG3(VEC3(0, 0, 0), VEC3(draw_size.x, draw_size.y, 1));
|
|
Rng2 draw_scissor = RNG2(VEC2(0, 0), VEC2(draw_size.x, draw_size.y));
|
|
|
|
//////////////////////////////
|
|
//- Process commands
|
|
|
|
g->boxes_count = 0;
|
|
|
|
for (UI_CmdNode *cmd_node = frame->first_cmd_node; cmd_node; cmd_node = cmd_node->next)
|
|
{
|
|
UI_Cmd cmd = cmd_node->cmd;
|
|
|
|
switch (cmd.kind)
|
|
{
|
|
case UI_CmdKind_BuildBox:
|
|
{
|
|
UI_Key key = cmd.box.key;
|
|
if (key.hash == 0)
|
|
{
|
|
key = UI_TransKey();
|
|
}
|
|
|
|
b32 is_root = frame->root_box == 0;
|
|
UI_Box *parent = 0;
|
|
if (!is_root)
|
|
{
|
|
parent = UI_BoxFromKey(cmd.box.parent);
|
|
if (!parent)
|
|
{
|
|
parent = frame->root_box;
|
|
}
|
|
}
|
|
|
|
/* Allocate box */
|
|
UI_Box *box = 0;
|
|
{
|
|
UI_BoxBin *bin = &g->box_bins[key.hash % UI_NumBoxLookupBins];
|
|
for (UI_Box *tmp = bin->first; tmp && !box; tmp = tmp->next_in_bin)
|
|
{
|
|
if (tmp->key.hash == key.hash)
|
|
{
|
|
box = tmp;
|
|
}
|
|
}
|
|
if (box)
|
|
{
|
|
/* Remove box from old parent */
|
|
if (box->parent)
|
|
{
|
|
DllQueueRemove(box->parent->first, box->parent->last, box);
|
|
--box->parent->count;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
box = g->first_free_box;
|
|
if (box)
|
|
{
|
|
SllStackPop(g->first_free_box);
|
|
ZeroStruct(box);
|
|
}
|
|
else
|
|
{
|
|
box = PushStruct(g->box_arena, UI_Box);
|
|
}
|
|
DllQueuePushNP(bin->first, bin->last, box, next_in_bin, prev_in_bin);
|
|
}
|
|
}
|
|
++g->boxes_count;
|
|
|
|
/* Reset box */
|
|
UI_Box old_box = *box;
|
|
{
|
|
ZeroStruct(box);
|
|
box->next_in_bin = old_box.next_in_bin;
|
|
box->prev_in_bin = old_box.prev_in_bin;
|
|
box->report = old_box.report;
|
|
}
|
|
box->key = key;
|
|
box->last_updated_tick = frame->tick;
|
|
|
|
/* Update box */
|
|
{
|
|
box->desc = cmd.box;
|
|
|
|
/* Insert box into parent */
|
|
if (parent)
|
|
{
|
|
DllQueuePush(parent->first, parent->last, box);
|
|
box->parent = parent;
|
|
++parent->count;
|
|
}
|
|
|
|
/* Fetch run */
|
|
box->glyph_run = &GC_NilRun;
|
|
if (box->desc.text.len > 0)
|
|
{
|
|
box->glyph_run = GC_RunFromString(frame->arena, box->desc.font, box->desc.text);
|
|
}
|
|
}
|
|
|
|
if (is_root)
|
|
{
|
|
frame->root_box = box;
|
|
}
|
|
} break;
|
|
|
|
case UI_CmdKind_SetRawTexture:
|
|
{
|
|
UI_Key key = cmd.set_raw_texture.key;
|
|
UI_Box *box = UI_BoxFromKey(key);
|
|
if (box)
|
|
{
|
|
box->raw_texture = cmd.set_raw_texture.tex;
|
|
box->raw_texture_uv0 = cmd.set_raw_texture.uv0;
|
|
box->raw_texture_uv1 = cmd.set_raw_texture.uv1;
|
|
}
|
|
} break;
|
|
}
|
|
|
|
}
|
|
|
|
//////////////////////////////
|
|
//- Prune boxes
|
|
|
|
// {
|
|
// u64 cur_tick = frame->tick;
|
|
// UI_BoxIter it = UI_ITER(g->root_box);
|
|
// UI_Box *box = UI_NextBox(&it);
|
|
// while (box != 0)
|
|
// {
|
|
// UI_Box *next = UI_NextBox(&it);
|
|
// box = next;
|
|
// }
|
|
// }
|
|
|
|
//////////////////////////////
|
|
//- Layout
|
|
|
|
/* Build pre-order & post-order box arrays */
|
|
u64 boxes_count = g->boxes_count;
|
|
UI_Box **boxes_pre = PushStructsNoZero(frame->arena, UI_Box *, boxes_count);
|
|
UI_Box **boxes_post = PushStructsNoZero(frame->arena, UI_Box *, boxes_count);
|
|
frame->boxes_pre = boxes_pre;
|
|
frame->boxes_post = boxes_post;
|
|
{
|
|
Struct(BoxNode) { BoxNode *next; b32 visited; UI_Box *box; };
|
|
BoxNode *first_dfs = 0;
|
|
u64 pre_index = 0;
|
|
u64 post_index = 0;
|
|
{
|
|
BoxNode *n = PushStruct(scratch.arena, BoxNode);
|
|
n->box = frame->root_box;
|
|
SllStackPush(first_dfs, n);
|
|
}
|
|
while (first_dfs)
|
|
{
|
|
BoxNode *n = first_dfs;
|
|
UI_Box *box = n->box;
|
|
if (!n->visited)
|
|
{
|
|
/* Push floating children to dfs stack */
|
|
for (UI_Box *child = box->last; child; child = child->prev)
|
|
{
|
|
if (AnyBit(child->desc.flags, UI_BoxFlag_Floating))
|
|
{
|
|
BoxNode *child_n = PushStruct(scratch.arena, BoxNode);
|
|
child_n->box = child;
|
|
SllStackPush(first_dfs, child_n);
|
|
}
|
|
}
|
|
/* Push non-floating children to dfs stack */
|
|
for (UI_Box *child = box->last; child; child = child->prev)
|
|
{
|
|
if (!AnyBit(child->desc.flags, UI_BoxFlag_Floating))
|
|
{
|
|
BoxNode *child_n = PushStruct(scratch.arena, BoxNode);
|
|
child_n->box = child;
|
|
SllStackPush(first_dfs, child_n);
|
|
}
|
|
}
|
|
box->pre_index = pre_index++;
|
|
boxes_pre[box->pre_index] = box;
|
|
n->visited = 1;
|
|
}
|
|
else
|
|
{
|
|
SllStackPop(first_dfs);
|
|
box->post_index = post_index++;
|
|
boxes_post[box->post_index] = box;
|
|
}
|
|
}
|
|
Assert(pre_index == boxes_count);
|
|
Assert(post_index == boxes_count);
|
|
}
|
|
|
|
/* Compute independent sizes */
|
|
for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index)
|
|
{
|
|
UI_Box *box = boxes_pre[pre_index];
|
|
for (Axis axis = 0; axis < Axis_CountXY; ++axis)
|
|
{
|
|
UI_Size pref_size = box->desc.pref_size[axis];
|
|
if (pref_size.kind == UI_SizeKind_Pixel)
|
|
{
|
|
box->solved_dims[axis] = pref_size.v;
|
|
}
|
|
else if (pref_size.kind == UI_SizeKind_Shrink && AnyBit(box->desc.flags, UI_BoxFlag_DrawText))
|
|
{
|
|
/* TODO: Distinguish between baseline alignment & visual alignment */
|
|
f32 text_size = 0;
|
|
if (axis == Axis_X)
|
|
{
|
|
GC_RunRect rr = box->glyph_run->rects[box->glyph_run->count - 1];
|
|
f32 baseline_length = rr.pos + rr.advance;
|
|
text_size = baseline_length;
|
|
}
|
|
else
|
|
{
|
|
text_size = box->glyph_run->ascent + box->glyph_run->descent;
|
|
}
|
|
box->solved_dims[axis] = text_size + (pref_size.v * 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Compute upwards-dependent sizes along layout axis */
|
|
for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index)
|
|
{
|
|
UI_Box *box = boxes_pre[pre_index];
|
|
if (box->parent)
|
|
{
|
|
Axis axis = box->parent->desc.child_layout_axis;
|
|
UI_Size pref_size = box->desc.pref_size[axis];
|
|
if (pref_size.kind == UI_SizeKind_Grow)
|
|
{
|
|
f32 match_size = 0;
|
|
b32 found_match = 0;
|
|
for (UI_Box *ancestor = box->parent; ancestor != 0 && !found_match; ancestor = ancestor->parent)
|
|
{
|
|
UI_Size ancestor_size = ancestor->desc.pref_size[axis];
|
|
if (ancestor_size.kind == UI_SizeKind_Pixel || (ancestor_size.kind == UI_SizeKind_Shrink && AnyBit(box->desc.flags, UI_BoxFlag_DrawText)))
|
|
{
|
|
/* Match independent ancestor */
|
|
match_size = ancestor->solved_dims[axis];
|
|
found_match = 1;
|
|
}
|
|
}
|
|
box->solved_dims[axis] = match_size * pref_size.v;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Compute downwards-dependent sizes */
|
|
for (u64 post_index = 0; post_index < boxes_count; ++post_index)
|
|
{
|
|
UI_Box *box = boxes_post[post_index];
|
|
for (Axis axis = 0; axis < Axis_CountXY; ++axis)
|
|
{
|
|
UI_Size pref_size = box->desc.pref_size[axis];
|
|
if (pref_size.kind == UI_SizeKind_Shrink && !AnyBit(box->desc.flags, UI_BoxFlag_DrawText))
|
|
{
|
|
f32 accum = 0;
|
|
for (UI_Box *child = box->first; child; child = child->next)
|
|
{
|
|
if (!AnyBit(child->desc.flags, UI_BoxFlag_Floating))
|
|
{
|
|
if (axis == box->desc.child_layout_axis)
|
|
{
|
|
accum += child->solved_dims[axis];
|
|
}
|
|
else
|
|
{
|
|
accum = MaxF32(child->solved_dims[axis], accum);
|
|
}
|
|
}
|
|
}
|
|
box->solved_dims[axis] = accum + (pref_size.v * 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Compute upwards-dependent sizes along non-layout axis */
|
|
for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index)
|
|
{
|
|
UI_Box *box = boxes_pre[pre_index];
|
|
if (box->parent)
|
|
{
|
|
Axis axis = !box->parent->desc.child_layout_axis;
|
|
UI_Size pref_size = box->desc.pref_size[axis];
|
|
if (pref_size.kind == UI_SizeKind_Grow)
|
|
{
|
|
box->solved_dims[axis] = box->parent->solved_dims[axis] * pref_size.v;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Solve violations */
|
|
for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index)
|
|
{
|
|
UI_Box *box = boxes_pre[pre_index];
|
|
for (Axis axis = 0; axis < Axis_CountXY; ++axis)
|
|
{
|
|
f32 box_size = box->solved_dims[axis];
|
|
/* Solve non-floating violations */
|
|
{
|
|
f32 size_accum = 0;
|
|
f32 flex_accum = 0;
|
|
for (UI_Box *child = box->first; child; child = child->next)
|
|
{
|
|
if (!AnyBit(child->desc.flags, UI_BoxFlag_Floating))
|
|
{
|
|
f32 size = child->solved_dims[axis];
|
|
f32 strictness = child->desc.pref_size[axis].strictness;
|
|
f32 flex = size * (1.0 - strictness);
|
|
if (axis == box->desc.child_layout_axis)
|
|
{
|
|
size_accum += size;
|
|
flex_accum += flex;
|
|
}
|
|
else
|
|
{
|
|
size_accum = MaxF32(size_accum, size);
|
|
flex_accum = MaxF32(flex_accum, flex);
|
|
}
|
|
}
|
|
}
|
|
f32 violation = size_accum - box_size;
|
|
if (violation > 0 && flex_accum > 0)
|
|
{
|
|
f32 adjusted_size_accum = 0;
|
|
for (UI_Box *child = box->first; child; child = child->next)
|
|
{
|
|
if (!AnyBit(child->desc.flags, UI_BoxFlag_Floating))
|
|
{
|
|
f32 size = child->solved_dims[axis];
|
|
f32 strictness = child->desc.pref_size[axis].strictness;
|
|
f32 flex = size * (1.0 - strictness);
|
|
f32 new_size = size;
|
|
if (axis == box->desc.child_layout_axis)
|
|
{
|
|
f32 chopoff = MinF32(flex, violation * (flex / flex_accum));
|
|
new_size = size - chopoff;
|
|
}
|
|
else
|
|
{
|
|
if (size > box_size)
|
|
{
|
|
new_size = MaxF32(size - flex, box_size);
|
|
}
|
|
}
|
|
adjusted_size_accum += new_size;
|
|
child->solved_dims[axis] = new_size;
|
|
}
|
|
}
|
|
size_accum = adjusted_size_accum;
|
|
}
|
|
box->final_children_size_accum[axis] = size_accum;
|
|
}
|
|
/* Solve floating violations */
|
|
for (UI_Box *child = box->first; child; child = child->next)
|
|
{
|
|
if (AnyBit(child->desc.flags, UI_BoxFlag_Floating) && !AnyBit(child->desc.flags, UI_BoxFlag_NoFloatingClamp))
|
|
{
|
|
f32 size = child->solved_dims[axis];
|
|
if (size > box_size)
|
|
{
|
|
f32 strictness = child->desc.pref_size[axis].strictness;
|
|
f32 flex = size * (1.0 - strictness);
|
|
child->solved_dims[axis] = MaxF32(size - flex, box_size);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Compute final positions */
|
|
for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index)
|
|
{
|
|
UI_Box *box = boxes_pre[pre_index];
|
|
UI_Box *parent = box->parent;
|
|
|
|
/* Initialize layout cursor based on alignment */
|
|
{
|
|
Axis axis = box->desc.child_layout_axis;
|
|
UI_AxisAlignment alignment = box->desc.child_alignment[axis];
|
|
f32 box_size = box->solved_dims[axis];
|
|
f32 size_accum = box->final_children_size_accum[axis];
|
|
switch(alignment)
|
|
{
|
|
default: break;
|
|
case UI_AxisAlignment_Center:
|
|
{
|
|
box->layout_cursor = box_size / 2 - size_accum / 2;
|
|
} break;
|
|
case UI_AxisAlignment_End:
|
|
{
|
|
box->layout_cursor = box_size - size_accum;
|
|
} break;
|
|
}
|
|
}
|
|
|
|
/* Position */
|
|
{
|
|
f32 *dims_arr = box->solved_dims;
|
|
Vec2 dims_vec = VEC2(dims_arr[0], dims_arr[1]);
|
|
Vec2 final_pos = ZI;
|
|
|
|
/* Floating box position */
|
|
if (AnyBit(box->desc.flags, UI_BoxFlag_Floating))
|
|
{
|
|
Vec2 offset = box->desc.floating_pos;
|
|
final_pos = AddVec2(parent->p0, offset);
|
|
if (!AnyBit(box->desc.flags, UI_BoxFlag_NoFloatingClamp))
|
|
{
|
|
{
|
|
f32 overshoot = MaxF32(0, (final_pos.x + dims_vec.x) - parent->p1.x);
|
|
final_pos.x = MaxF32(parent->p0.x, final_pos.x - overshoot);
|
|
}
|
|
{
|
|
f32 overshoot = MaxF32((final_pos.y + dims_vec.y) - parent->p1.y, 0);
|
|
final_pos.y = MaxF32(parent->p0.y, final_pos.y - overshoot);
|
|
}
|
|
}
|
|
}
|
|
/* Non-floating box position */
|
|
else if (parent)
|
|
{
|
|
f32 layout_cursor = parent->layout_cursor;
|
|
f32 offset[2] = ZI;
|
|
/* Compute offset in layout direction */
|
|
{
|
|
Axis axis = parent->desc.child_layout_axis;
|
|
offset[axis] = layout_cursor;
|
|
}
|
|
/* Compute offset in non-layout direction (based on alignment) */
|
|
{
|
|
Axis axis = !parent->desc.child_layout_axis;
|
|
UI_AxisAlignment alignment = parent->desc.child_alignment[axis];
|
|
switch(alignment)
|
|
{
|
|
default: break;
|
|
case UI_AxisAlignment_Center:
|
|
{
|
|
f32 parent_size = parent->solved_dims[axis];
|
|
f32 box_size = dims_arr[axis];
|
|
offset[axis] = parent_size / 2 - box_size / 2;
|
|
} break;
|
|
case UI_AxisAlignment_End:
|
|
{
|
|
f32 parent_size = parent->solved_dims[axis];
|
|
f32 box_size = dims_arr[axis];
|
|
offset[axis] = parent_size - box_size;
|
|
} break;
|
|
}
|
|
}
|
|
final_pos.x = parent->p0.x + offset[0];
|
|
final_pos.y = parent->p0.y + offset[1];
|
|
parent->layout_cursor += dims_arr[parent->desc.child_layout_axis];
|
|
}
|
|
|
|
/* Submit position */
|
|
Vec2 floored_final_pos = FloorVec2(final_pos);
|
|
Vec2 ceiled_dims = CeilVec2(dims_vec);
|
|
box->p0 = FloorVec2(floored_final_pos);
|
|
box->p1 = AddVec2(floored_final_pos, ceiled_dims);
|
|
}
|
|
|
|
/* Rounding */
|
|
{
|
|
UI_Round rounding = box->desc.rounding;
|
|
Vec2 half_dims = MulVec2(SubVec2(box->p1, box->p0), 0.5);
|
|
f32 min_half_dims = MinF32(half_dims.x, half_dims.y);
|
|
f32 final_rounding_tl = 0;
|
|
f32 final_rounding_tr = 0;
|
|
f32 final_rounding_br = 0;
|
|
f32 final_rounding_bl = 0;
|
|
|
|
switch(rounding.kind)
|
|
{
|
|
default: break;
|
|
case UI_RoundKind_Pixel:
|
|
{
|
|
final_rounding_tl = rounding.v;
|
|
final_rounding_tr = final_rounding_tl;
|
|
final_rounding_br = final_rounding_tl;
|
|
final_rounding_bl = final_rounding_tl;
|
|
} break;
|
|
case UI_RoundKind_Grow:
|
|
{
|
|
final_rounding_tl = rounding.v * min_half_dims;
|
|
final_rounding_tr = final_rounding_tl;
|
|
final_rounding_br = final_rounding_tl;
|
|
final_rounding_bl = final_rounding_tl;
|
|
} break;
|
|
}
|
|
|
|
if (parent && !AllBits(box->desc.flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClamp))
|
|
{
|
|
Vec2 vtl = SubVec2(VEC2(parent->p0.x, parent->p0.y), VEC2(box->p0.x, box->p0.y));
|
|
Vec2 vtr = SubVec2(VEC2(parent->p1.x, parent->p0.y), VEC2(box->p1.x, box->p0.y));
|
|
Vec2 vbr = SubVec2(VEC2(parent->p1.x, parent->p1.y), VEC2(box->p1.x, box->p1.y));
|
|
Vec2 vbl = SubVec2(VEC2(parent->p0.x, parent->p1.y), VEC2(box->p0.x, box->p1.y));
|
|
final_rounding_tl = MaxF32(final_rounding_tl, parent->rounding_tl - Vec2Len(vtl));
|
|
final_rounding_tr = MaxF32(final_rounding_tr, parent->rounding_tr - Vec2Len(vtr));
|
|
final_rounding_br = MaxF32(final_rounding_br, parent->rounding_br - Vec2Len(vbr));
|
|
final_rounding_bl = MaxF32(final_rounding_bl, parent->rounding_bl - Vec2Len(vbl));
|
|
}
|
|
|
|
/* Submit rounding */
|
|
box->rounding_tl = final_rounding_tl;
|
|
box->rounding_tr = final_rounding_tr;
|
|
box->rounding_br = final_rounding_br;
|
|
box->rounding_bl = final_rounding_bl;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////
|
|
//- Render
|
|
|
|
G_ResourceHandle backbuffer = frame->window_frame.backbuffer;
|
|
{
|
|
//////////////////////////////
|
|
//- Build render data
|
|
|
|
/* Build rect instance data */
|
|
for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index)
|
|
{
|
|
UI_Box *box = boxes_pre[pre_index];
|
|
b32 is_visible = 1;
|
|
is_visible = is_visible && (box->desc.tint.w != 0);
|
|
is_visible = is_visible && (box->p1.x > box->p0.x);
|
|
is_visible = is_visible && (box->p1.y > box->p0.y);
|
|
if (is_visible || AnyBit(frame->frame_flags, UI_FrameFlag_Debug))
|
|
{
|
|
|
|
/* Box rect */
|
|
{
|
|
UI_DRect *rect = PushStruct(frame->rects_arena, UI_DRect);
|
|
rect->p0 = box->p0;
|
|
rect->p1 = box->p1;
|
|
rect->tex_uv0 = VEC2(0, 0);
|
|
rect->tex_uv1 = VEC2(1, 1);
|
|
rect->background_lin = LinearFromSrgb(box->desc.background_color);
|
|
rect->border_lin = LinearFromSrgb(box->desc.border_color);
|
|
rect->debug_lin = LinearFromSrgb(box->desc.debug_color);
|
|
rect->tint_lin = LinearFromSrgb(box->desc.tint);
|
|
rect->border = box->desc.border;
|
|
rect->tl_rounding = box->rounding_tl;
|
|
rect->tr_rounding = box->rounding_tr;
|
|
rect->br_rounding = box->rounding_br;
|
|
rect->bl_rounding = box->rounding_bl;
|
|
rect->tex = box->raw_texture;
|
|
rect->tex_uv0 = box->raw_texture_uv0;
|
|
rect->tex_uv1 = box->raw_texture_uv1;
|
|
}
|
|
|
|
/* Text rects */
|
|
GC_Run *raw_run = box->glyph_run;
|
|
if (AnyBit(box->desc.flags, UI_BoxFlag_DrawText) && raw_run->loaded)
|
|
{
|
|
f32 max_baseline = box->p1.x - box->p0.x;
|
|
b32 should_truncate = raw_run->count > 0 && (raw_run->rects[raw_run->count - 1].pos + raw_run->rects[raw_run->count - 1].advance) > max_baseline;
|
|
|
|
/* Truncate run */
|
|
GC_RunRect *truncated_rects = raw_run->rects;
|
|
u32 truncated_rects_count = raw_run->count;
|
|
if (should_truncate && !AnyBit(box->desc.flags, UI_BoxFlag_NoTextTruncation))
|
|
{
|
|
/* Get elipses run */
|
|
GC_Run *elipses_run = GC_RunFromString(scratch.arena, box->desc.font, Lit("..."));
|
|
if (elipses_run->count > 0)
|
|
{
|
|
max_baseline -= elipses_run->rects[elipses_run->count - 1].pos + elipses_run->rects[elipses_run->count - 1].advance;
|
|
}
|
|
|
|
/* Subtract glyphs */
|
|
while (truncated_rects_count > 0)
|
|
{
|
|
GC_RunRect rr = raw_run->rects[truncated_rects_count - 1];
|
|
if (rr.pos + rr.advance <= max_baseline)
|
|
{
|
|
break;
|
|
}
|
|
--truncated_rects_count;
|
|
}
|
|
|
|
/* Merge trunc rects */
|
|
/* FIXME: Verify this stil works as expected */
|
|
{
|
|
truncated_rects_count += elipses_run->count;
|
|
truncated_rects = PushStructsNoZero(scratch.arena, GC_RunRect, truncated_rects_count + elipses_run->count);
|
|
CopyStructs(truncated_rects, raw_run->rects, raw_run->count);
|
|
f32 elipses_offset = truncated_rects_count > 0 ? (truncated_rects[truncated_rects_count - 1].pos + truncated_rects[truncated_rects_count - 1].advance) : 0;
|
|
for (u32 i = 0; i < elipses_run->count; ++i)
|
|
{
|
|
GC_RunRect *rr = &truncated_rects[i + truncated_rects_count];
|
|
*rr = elipses_run->rects[i];
|
|
rr->pos += elipses_offset;
|
|
}
|
|
}
|
|
}
|
|
|
|
UI_AxisAlignment x_alignment = box->desc.child_alignment[Axis_X];
|
|
UI_AxisAlignment y_alignment = box->desc.child_alignment[Axis_Y];
|
|
if (should_truncate)
|
|
{
|
|
x_alignment = UI_AxisAlignment_Start;
|
|
}
|
|
|
|
/* Compute baseline */
|
|
f32 ascent = raw_run->ascent;
|
|
f32 descent = raw_run->descent;
|
|
f32 cap = raw_run->cap;
|
|
f32 baseline_width = truncated_rects_count > 0 ? (truncated_rects[truncated_rects_count - 1].pos + truncated_rects[truncated_rects_count - 1].advance) : 0;
|
|
f32 baseline_height = ascent + descent;
|
|
f32 box_width = box->p1.x - box->p0.x;
|
|
f32 box_height = box->p1.y - box->p0.y;
|
|
Vec2 baseline = ZI;
|
|
switch (x_alignment)
|
|
{
|
|
case UI_AxisAlignment_Start:
|
|
{
|
|
baseline.x = box->p0.x;
|
|
} break;
|
|
case UI_AxisAlignment_End:
|
|
{
|
|
baseline.x = box->p1.x;
|
|
baseline.x -= baseline_width;
|
|
} break;
|
|
case UI_AxisAlignment_Center:
|
|
{
|
|
baseline.x = box->p0.x;
|
|
baseline.x += (box_width - baseline_width) / 2;
|
|
} break;
|
|
}
|
|
switch (y_alignment)
|
|
{
|
|
case UI_AxisAlignment_Start:
|
|
{
|
|
baseline.y = box->p0.y;
|
|
baseline.y += ascent;
|
|
} break;
|
|
case UI_AxisAlignment_End:
|
|
{
|
|
baseline.y = box->p1.y;
|
|
baseline.y -= descent;
|
|
} break;
|
|
case UI_AxisAlignment_Center:
|
|
{
|
|
baseline.y = box->p0.y;
|
|
baseline.y += box_height / 2;
|
|
baseline.y += cap / 2;
|
|
} break;
|
|
}
|
|
baseline = CeilVec2(baseline);
|
|
|
|
/* Push text rects */
|
|
for (u64 i = 0; i < truncated_rects_count; ++i)
|
|
{
|
|
GC_RunRect rr = truncated_rects[i];
|
|
Vec2 glyph_size = rr.size;
|
|
if (glyph_size.x != 0 || glyph_size.y != 0)
|
|
{
|
|
UI_DRect *rect = PushStruct(frame->rects_arena, UI_DRect);
|
|
rect->p0 = AddVec2(baseline, VEC2(rr.pos, 0));
|
|
rect->p0 = AddVec2(rect->p0, rr.offset);
|
|
rect->p1 = AddVec2(rect->p0, glyph_size);
|
|
rect->debug_lin = LinearFromSrgb(box->desc.debug_color);
|
|
rect->tint_lin = LinearFromSrgb(box->desc.tint);
|
|
rect->tex = rr.tex;
|
|
rect->tex_uv0 = rr.uv.p0;
|
|
rect->tex_uv1 = rr.uv.p1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////
|
|
//- Push data to GPU
|
|
|
|
/* Target */
|
|
G_ResourceHandle draw_target = G_PushTexture2D(
|
|
frame->gpu_arena,
|
|
G_Format_R16G16B16A16_Float,
|
|
monitor_size,
|
|
G_Layout_DirectQueue_RenderTargetWrite,
|
|
.flags = G_ResourceFlag_AllowRenderTarget
|
|
);
|
|
G_Texture2DRef draw_target_ro = G_PushTexture2DRef(frame->gpu_arena, draw_target);
|
|
|
|
/* Rects */
|
|
G_ResourceHandle rects_buff = G_PushBufferFromString(frame->gpu_arena, frame->cl, StringFromArena(frame->rects_arena));
|
|
G_StructuredBufferRef rects_ro = G_PushStructuredBufferRef(frame->gpu_arena, rects_buff, UI_DRect);
|
|
|
|
/* Params */
|
|
UI_DParams params = ZI;
|
|
{
|
|
params.target_size = draw_size;
|
|
params.target_ro = draw_target_ro;
|
|
params.rects = rects_ro;
|
|
params.sampler = G_BasicSampler();
|
|
}
|
|
G_ResourceHandle params_buff = G_PushBufferFromString(frame->gpu_arena, frame->cl, StringFromStruct(¶ms));
|
|
G_StructuredBufferRef params_ro = G_PushStructuredBufferRef(frame->gpu_arena, params_buff, UI_DParams);
|
|
|
|
/* Constants */
|
|
G_SetConstant(frame->cl, UI_ShaderConst_Params, params_ro);
|
|
|
|
//////////////////////////////
|
|
//- Dispatch shaders
|
|
|
|
G_DumbMemoryLayoutSync(frame->cl, draw_target, G_Layout_DirectQueue_RenderTargetWrite);
|
|
|
|
//- Clear pass
|
|
{
|
|
G_ClearRenderTarget(frame->cl, draw_target, VEC4(1, 0, 0, 1));
|
|
}
|
|
|
|
//- Rect pass
|
|
|
|
G_DumbMemoryLayoutSync(frame->cl, draw_target, G_Layout_DirectQueue_RenderTargetWrite);
|
|
|
|
if (G_CountBufferBytes(rects_buff) > 0)
|
|
{
|
|
/* Render rects */
|
|
G_Rasterize(frame->cl,
|
|
UI_DRectVS, UI_DRectPS,
|
|
1, G_QuadIndices(),
|
|
1, &draw_target,
|
|
draw_viewport, draw_scissor,
|
|
G_RasterMode_TriangleList);
|
|
|
|
/* Render rect wireframes */
|
|
if (AnyBit(frame->frame_flags, UI_FrameFlag_Debug))
|
|
{
|
|
G_SetConstant(frame->cl, UI_ShaderConst_DebugDraw, 1);
|
|
G_Rasterize(frame->cl,
|
|
UI_DRectVS, UI_DRectPS,
|
|
1, G_QuadIndices(),
|
|
1, &draw_target,
|
|
draw_viewport, draw_scissor,
|
|
G_RasterMode_WireTriangleList);
|
|
}
|
|
}
|
|
|
|
//- Backbuffer blit pass
|
|
|
|
G_DumbMemoryLayoutSync(frame->cl, draw_target, G_Layout_DirectQueue_ShaderRead);
|
|
G_DumbMemoryLayoutSync(frame->cl, backbuffer, G_Layout_DirectQueue_RenderTargetWrite);
|
|
|
|
{
|
|
G_Rasterize(frame->cl,
|
|
UI_BlitVS, UI_BlitPS,
|
|
1, G_QuadIndices(),
|
|
1, &backbuffer,
|
|
draw_viewport, draw_scissor,
|
|
G_RasterMode_TriangleList);
|
|
}
|
|
|
|
G_DumbMemoryLayoutSync(frame->cl, backbuffer, G_Layout_AnyQueue_ShaderRead_CopyRead_CopyWrite_Present);
|
|
}
|
|
|
|
//////////////////////////////
|
|
//- End frame
|
|
|
|
G_CommitCommandList(frame->cl);
|
|
WND_EndFrame(frame->window_frame, VSYNC);
|
|
|
|
EndScratch(scratch);
|
|
}
|