#include "ui.h" #include "Color.h" UI_Context *__UI_current_ctx__ = NULL; UI_Context ui_initContext(Arena *arena, Renderer *renderer) { UI_RectList prevList = PushListZero(arena, UI_RectList, Thousand(10)); UI_RectList list = PushListZero(arena, UI_RectList, Thousand(10)); ListAppend(list, ((UI_Rect){0})); // empty item return (UI_Context){ .arena=arena, .rects=list, .prevRects=prevList, .hotNode=0, .prevHotNode=0, .cursorType=UI_Cursor_Arrow, .scene3DHandle=0, .input=NULL, .prevInput=NULL, .renderer=renderer, }; } void ui_attachTextAttr(UI_Context *ui, UI_Rect *rect, UI_RectStr strData) { UI_RectStr *strDataCopy = PushStruct(ui->arena, UI_RectStr); *strDataCopy = strData; if (strDataCopy->fontSize == -1) { strDataCopy->fontSize = rect->inheritedTextAttr->fontSize; if (strDataCopy->fontSize == -1) { strDataCopy->fontSize = ui->renderer->activeFont->lineHeight; } } if (strDataCopy->color.r == -1) { strDataCopy->color = rect->inheritedTextAttr->color; } if (strDataCopy->lineHeight == -1) { strDataCopy->lineHeight = rect->inheritedTextAttr->lineHeight; if (strDataCopy->lineHeight == -1) { strDataCopy->lineHeight = strDataCopy->fontSize; } if (strDataCopy->fontSize > strDataCopy->lineHeight) { strDataCopy->lineHeight = strDataCopy->fontSize; } } if (strDataCopy->alignment == -1) { if (rect->inheritedTextAttr->alignment != -1) { strDataCopy->alignment = rect->inheritedTextAttr->alignment; } else { strDataCopy->alignment = UI_TxtAlign_Left; } } rect->inheritedTextAttr = strDataCopy; if (strData.s.str != NULL) { strDataCopy->s = PushString(ui->arena, strData.s.length); memcpy(strDataCopy->s.str, strData.s.str, strData.s.length); rect->stringData = strDataCopy; } else { // NOTE(djledda): Not real text data if empty string -> just style attributes. Ignore } } void ui_placeText(UI_Context *ui, UI_RectStr strData) { ui_attachTextAttr(ui, &ui->rects.data[ui->currRect], strData); } void ui_sizingPass(UI_Context *ui, bool isXAxis, int32 rectHandle) { UI_Rect *rect = &ui->rects.data[rectHandle]; if (!rect->firstChild) return; bool isVertical = rect->flags & UI_Flag_Vertical; real32 remainingSpace = (isXAxis ? rect->resolvedWidth - rect->padding.left - rect->padding.right : rect->resolvedHeight - rect->padding.top - rect->padding.bottom) + rect->childGap; int32 growableChildrenCount = 0; UI_Rect *child; int32 childHandle = rect->firstChild; if (isVertical != isXAxis) { while (childHandle) { child = &ui->rects.data[childHandle]; if (!(child->flags & UI_Flag_Pos_Absolute)) { if (child->flags & UI_Flag_HeightGrow && !isXAxis || child->flags & UI_Flag_WidthGrow && isXAxis) { growableChildrenCount++; } real32 childBreadth = isXAxis ? child->resolvedWidth : child->resolvedHeight; remainingSpace -= childBreadth + rect->childGap; } childHandle = child->nextSibling; } } real32 childBreadthInc = growableChildrenCount > 0 ? remainingSpace / (real32)growableChildrenCount : 0; childHandle = rect->firstChild; while (childHandle) { child = &ui->rects.data[childHandle]; if (isXAxis) { if (child->flags & UI_Flag_WidthGrow) { if (isVertical) { child->resolvedWidth = rect->resolvedWidth - rect->padding.left - rect->padding.right; } else { child->resolvedWidth += childBreadthInc; } } } else { if (child->flags & UI_Flag_HeightGrow) { if (!isVertical) { child->resolvedHeight = rect->resolvedHeight - rect->padding.top - rect->padding.bottom; } else { child->resolvedHeight += childBreadthInc; } } } ui_sizingPass(ui, isXAxis, childHandle); childHandle = child->nextSibling; } } void ui_calcLayout(UI_Context *ui, bool isXAxis, int32 rectHandle) { UI_Rect *rect = &ui->rects.data[rectHandle]; if (!rect->firstChild) return; bool isVertical = (rect->flags & UI_Flag_Vertical); real32 coord = isXAxis ? rect->x + rect->padding.left : rect->y + rect->padding.top; real32 parentCoord = coord; int32 childHandle = rect->firstChild; UI_Rect *child; while (childHandle) { child = &ui->rects.data[childHandle]; if (child->flags & UI_Flag_Pos_Absolute) { if (isXAxis) { child->x = rect->x; } else { child->y = rect->y; } } else { if (isXAxis) { child->x = child->flags & UI_Flag_Center && isVertical ? coord + (rect->resolvedWidth - rect->padding.left - rect->padding.right)/2 - child->resolvedWidth/2 : coord; } else { child->y = child->flags & UI_Flag_Center && !isVertical ? coord + (rect->resolvedHeight - rect->padding.top - rect->padding.bottom)/2 - child->resolvedHeight/2 : coord; } } if (isXAxis) { child->x += child->xOffset; } else { child->y += child->yOffset; } ui_calcLayout(ui, isXAxis, childHandle); if (!(child->flags & UI_Flag_Pos_Absolute) && !isVertical == isXAxis) { coord += isXAxis ? child->resolvedWidth : child->resolvedHeight; coord += rect->childGap; } childHandle = child->nextSibling; } } void ui_begin(UI_Context *ui) { __UI_current_ctx__ = ui; arenaFreeFrom(ui->arena, 0); ClearList(ui->prevRects); ListAppendList(ui->prevRects, ui->rects); ui->cursorType = UI_Cursor_Arrow; ui->prevHotNode = ui->hotNode; ui->prevHoveredNode = ui->hoveredNode; ui->hotNode = 0; ui->hoveredNode = 0; ui->scene3DHandle = 0; ui->rootRect = 0; ui->currRect = 0; ClearList(ui->rects); ListAppend(ui->rects, (UI_Rect){0}); ui_openElement(ui, (UI_Rect){ .width=ui->renderer->width, .height=ui->renderer->height, .color=(Vec4){0,0,0,0}}); UI_RectStr *inheritedTextAttrInit = PushStruct(ui->arena, UI_RectStr); *inheritedTextAttrInit = UI_TxtAttr(); ui->rects.data[ui->currRect].inheritedTextAttr = inheritedTextAttrInit; } void ui_end(UI_Context *ui) { ui_closeElement(ui); // 1) a. Calculate layout ui_sizingPass(ui, true, ui->rootRect); // x ui_sizingPass(ui, false, ui->rootRect); // y // 1) b. ui_calcLayout(ui, true, ui->rootRect); // x ui_calcLayout(ui, false, ui->rootRect); // y // 2) Create render commands: for (EachEl(ui->rects, UI_Rect, rect)) { rendererPlaceRectangle( ui->renderer, rect->x, rect->y, rect->resolvedWidth, rect->resolvedHeight, rect->color, rect->borderRadius, rect->borderThickness, rect->borderColor ); if (rect->stringData) { UI_RectStr *str = rect->stringData; real32 alignmentXOffset = 0; real32 alignmentYOffset = rect->padding.top; real32 textWidth = str->s.length * str->fontSize / ui->renderer->activeFont->lineHeight * ui->renderer->activeFont->charWidth; enum UI_TxtAlign alignment = str->alignment == -1 ? UI_TxtAlign_Left : str->alignment; if (alignment & UI_TxtAlign_Right) { alignmentXOffset = rect->resolvedWidth - rect->padding.right - textWidth; } else if (alignment & UI_TxtAlign_Center) { alignmentXOffset = rect->resolvedWidth/2.0 - textWidth/2.0; } else { alignmentXOffset = rect->padding.left; } rendererPlaceString( ui->renderer, str->s, rect->x + alignmentXOffset + str->xOffset, rect->y + alignmentYOffset + str->yOffset, str->color, str->fontSize ); } } if (ui->scene3DHandle) { UI_Rect *scene3DRect = &ui->rects.data[ui->scene3DHandle]; ui->renderer->sceneX = (int32)scene3DRect->x; ui->renderer->sceneY = (int32)scene3DRect->y; ui->renderer->sceneWidth = (int32)scene3DRect->resolvedWidth; ui->renderer->sceneHeight = (int32)scene3DRect->resolvedHeight; } __UI_current_ctx__ = NULL; } static bool pointInRect(real32 x, real32 y, UI_Rect rect) { return x > rect.x && y > rect.y && x < (rect.x + rect.resolvedWidth) && y < (rect.y + rect.resolvedHeight); } void ui_openElement(UI_Context *ui, UI_Rect rect) { ListAppend(ui->rects, (UI_Rect){0}); int32 newHandle = ui->rects.length - 1; UI_Rect *newRect = &ui->rects.data[newHandle]; UI_Rect *currRect = &ui->rects.data[ui->currRect]; *newRect = rect; newRect->parent = ui->currRect; if (currRect->lastChild) { ui->rects.data[currRect->lastChild].nextSibling = newHandle; } else { currRect->firstChild = newHandle; } currRect->lastChild = newHandle; if (!ui->rootRect) { ui->rootRect = newHandle; } ui->currRect = newHandle; if (newRect->flags & UI_Flag_3DScene) { ui->scene3DHandle = newHandle; } if (currRect->stringData) { UI_RectStr *inheritedTextAttr = PushStructZero(ui->arena, UI_RectStr); *inheritedTextAttr = *currRect->stringData; *newRect->inheritedTextAttr = *currRect->stringData; newRect->inheritedTextAttr->s = (string){ .str=NULL, .length=0 }; } else if (newRect->inheritedTextAttr == NULL && currRect) { newRect->inheritedTextAttr = currRect->inheritedTextAttr; } } void ui_closeElement(UI_Context *ui) { UI_Rect *currRect = &ui->rects.data[ui->currRect]; UI_Rect *parentRect = &ui->rects.data[currRect->parent]; if (currRect->width != -1) { currRect->resolvedWidth = currRect->width; } else if (currRect->minWidth == -1) { if (currRect->stringData) { real32 charWidth = currRect->stringData->fontSize / ui->renderer->activeFont->lineHeight * ui->renderer->activeFont->charWidth; currRect->minWidth = charWidth*currRect->stringData->s.length; } else { currRect->minWidth = 0; } } if (currRect->height != -1) { currRect->resolvedHeight = currRect->height; } else if (currRect->minHeight == -1) { if (currRect->stringData) { currRect->minHeight = currRect->stringData->lineHeight; } else { currRect->minHeight = 0; } } bool vertical = parentRect->flags & UI_Flag_Vertical; real32 currBreadth = vertical ? currRect->resolvedHeight : currRect->resolvedWidth; real32 parentBreadth = vertical ? parentRect->height : parentRect->width; real32 gap = parentRect->childGap; real32 breadthInc = (parentRect->firstChild == ui->currRect ? 0 : gap) + currBreadth; if (parentBreadth == -1) { if (vertical) { parentRect->resolvedHeight += breadthInc; } else { parentRect->resolvedWidth += breadthInc; } } real32 currCrossBreadth = vertical ? currRect->resolvedWidth : currRect->resolvedHeight; real32 parentCrossBreadth = vertical ? parentRect->width : parentRect->height; real32 currParentCrossBreadth = vertical ? parentRect->resolvedWidth : parentRect->resolvedHeight; if (parentCrossBreadth == -1) { real32 newCrossBreadth = currCrossBreadth > currParentCrossBreadth ? currCrossBreadth : currParentCrossBreadth; if (vertical) { parentRect->resolvedWidth = newCrossBreadth; } else { parentRect->resolvedHeight = newCrossBreadth; } } ui->currRect = ui->rects.data[ui->currRect].parent; } /** * Returns whether the checkbox was clicked */ bool ui_ButtonWithHover(UI_Context *ui, UI_Rect rect, UI_Rect hovered, UI_RectStr textAttr) { int32 id = UI_NextID(); bool clicked = false; bool inRect = pointInRect(ui->input->mouse.point.x, ui->input->mouse.point.y, ui->prevRects.data[id]); if (inRect) { ui->cursorType = UI_Cursor_Pointer; if (ui->prevHotNode == id && !ui->input->mouse.btnLeft) { clicked = true; } else if (ui->input->mouse.btnLeft && (!ui->prevInput->mouse.btnLeft || ui->prevHotNode == id)) { ui->hotNode = id; } ui->hoveredNode = id; } bool useHover = inRect && !(hovered.flags & UI_Flag_Ignore); UI_Rect *rectToRender = useHover ? &hovered : ▭ if (rectToRender->borderRadius == -1) { rectToRender->borderRadius = 5; } UI_FromRect(*rectToRender) ui_placeText(ui, textAttr); return clicked; } UI_Rect ui_HoverRect(UI_Context *ui, UI_Rect rect, UI_Rect hovered) { int32 id = UI_NextID(); bool inRect = pointInRect(ui->input->mouse.point.x, ui->input->mouse.point.y, ui->prevRects.data[id]); if (inRect) { ui->hoveredNode = id; } return inRect && !(hovered.flags & UI_Flag_Ignore) ? hovered : rect; } /** * Returns whether the checkbox was clicked */ bool ui_Button(UI_Context *ui, UI_Rect rect, UI_RectStr textAttr) { return ui_ButtonWithHover(ui, rect, UI_RectAttr(.flags=UI_Flag_Ignore), textAttr); } /** * Returns whether the checkbox was clicked */ bool ui_CheckboxRect(UI_Context *ui, bool *value, UI_Rect rect) { int32 id = UI_NextID(); bool clicked = false; bool pointerInRect = pointInRect(ui->input->mouse.point.x, ui->input->mouse.point.y, ui->prevRects.data[id]); if (pointerInRect) { ui->cursorType = UI_Cursor_Pointer; if (ui->prevHotNode == id && !ui->input->mouse.btnLeft) { *value = !*value; clicked = true; } else if (ui->input->mouse.btnLeft && (!ui->prevInput->mouse.btnLeft || ui->prevHotNode == id)) { ui->hotNode = id; } } if (*value) { rect.borderRadius = 5; rect.borderThickness = 0; if (pointerInRect) { rect.color.x += 0.5; rect.color.y += 0.5; rect.color.z += 0.5; rect.color.x = rect.color.x <= 1.0 ? rect.color.x : 1.0; rect.color.y = rect.color.y <= 1.0 ? rect.color.y : 1.0; rect.color.z = rect.color.z <= 1.0 ? rect.color.z : 1.0; } UI_FromRect(rect); } else { rect.borderRadius = 5; rect.borderThickness = 2; rect.borderColor = COLOR_WHITE; if (pointerInRect) { rect.color = COLOR_WHITE; rect.color.w = 0.2; } else { rect.color = (Vec4){0,0,0,0}; } UI_FromRect(rect); } return clicked; }