Addded scrollable boxes.

main
sixtenhugosson 2023-06-27 16:14:28 +02:00
parent c9f125cc56
commit 4dd7703810
22 changed files with 558 additions and 121 deletions

View File

@ -507,6 +507,12 @@ inline r32 DimOfRange(range1_r32 Range)
return(Result);
}
inline range1_r32 Intersection(range1_r32 A, range1_r32 B)
{
range1_r32 Result = {Max(A.Min, B.Min), Min(A.Max, B.Max)};
return(Result);
}
inline range1_s32 Range1S32(s32 Min, s32 Max)
{
range1_s32 Result = {Min, Max};
@ -525,6 +531,12 @@ inline s32 DimOfRange(range1_s32 Range)
return(Result);
}
inline range1_s32 Intersection(range1_s32 A, range1_s32 B)
{
range1_s32 Result = {Max(A.Min, B.Min), Min(A.Max, B.Max)};
return(Result);
}
inline range1_s64 Range1S64(s64 Min, s64 Max)
{
range1_s64 Result = {Min, Max};
@ -543,6 +555,12 @@ inline s64 DimOfRange(range1_s64 Range)
return(Result);
}
inline range1_s64 Intersection(range1_s64 A, range1_s64 B)
{
range1_s64 Result = {Max(A.Min, B.Min), Min(A.Max, B.Max)};
return(Result);
}
inline range2_r32 Range2R32(v2_r32 Min, v2_r32 Max)
{
range2_r32 Result = {Min, Max};
@ -562,6 +580,12 @@ inline v2_r32 DimOfRange(range2_r32 Range)
return(Result);
}
inline range2_r32 Intersection(range2_r32 A, range2_r32 B)
{
range2_r32 Result = {Max(A.Min, B.Min), Min(A.Max, B.Max)};
return(Result);
}
inline range2_s32 Range2S32(v2_s32 Min, v2_s32 Max)
{
range2_s32 Result = {Min, Max};
@ -581,6 +605,12 @@ inline v2_s32 DimOfRange(range2_s32 Range)
return(Result);
}
inline range2_s32 Intersection(range2_s32 A, range2_s32 B)
{
range2_s32 Result = {Max(A.Min, B.Min), Min(A.Max, B.Max)};
return(Result);
}
inline range2_s64 Range2S64(v2_s64 Min, v2_s64 Max)
{
range2_s64 Result = {Min, Max};
@ -599,3 +629,9 @@ inline v2_s64 DimOfRange(range2_s64 Range)
v2_s64 Result = Range.Max - Range.Min;
return(Result);
}
inline range2_s64 Intersection(range2_s64 A, range2_s64 B)
{
range2_s64 Result = {Max(A.Min, B.Min), Min(A.Max, B.Max)};
return(Result);
}

View File

@ -209,6 +209,12 @@ inline r32 LinearBlend(r32 a, r32 b, r32 x) { r32 Result = a + (b-a)*x; return(R
inline r32 Min(r32 A, r32 B) { r32 Result = Minimum(A, B); return(Result); }
inline r32 Max(r32 A, r32 B) { r32 Result = Maximum(A, B); return(Result); }
inline s32 Min(s32 A, s32 B) { s32 Result = Minimum(A, B); return(Result); }
inline s32 Max(s32 A, s32 B) { s32 Result = Maximum(A, B); return(Result); }
inline s64 Min(s64 A, s64 B) { s64 Result = Minimum(A, B); return(Result); }
inline s64 Max(s64 A, s64 B) { s64 Result = Maximum(A, B); return(Result); }
//- sixten: Vector functions
inline v2_r32 V2R32(r32 x, r32 y);
@ -322,26 +328,32 @@ inline v4_s64 Max(v4_s64 A, v4_s64 B);
inline range1_r32 Range1R32(r32 Min, r32 Max);
inline b32 InRange(range1_r32 Range, r32 Value);
inline r32 DimOfRange(range1_r32 Range);
inline range1_r32 Intersection(range1_r32 A, range1_r32 B);
inline range1_s32 Range1S32(s32 Min, s32 Max);
inline b32 InRange(range1_s32 Range, s32 Value);
inline s32 DimOfRange(range1_s32 Range);
inline range1_s32 Intersection(range1_s32 A, range1_s32 B);
inline range1_s64 Range1S64(s64 Min, s64 Max);
inline b32 InRange(range1_s64 Range, s64 Value);
inline s64 DimOfRange(range1_s64 Range);
inline range1_s64 Intersection(range1_s64 A, range1_s64 B);
inline range2_r32 Range2R32(v2_r32 Min, v2_r32 Max);
inline b32 InRange(range2_r32 Range, v2_r32 Value);
inline v2_r32 DimOfRange(range2_r32 Range);
inline range2_r32 Intersection(range2_r32 A, range2_r32 B);
inline range2_s32 Range2S32(v2_s32 Min, v2_s32 Max);
inline b32 InRange(range2_s32 Range, v2_s32 Value);
inline v2_s32 DimOfRange(range2_s32 Range);
inline range2_s32 Intersection(range2_s32 A, range2_s32 B);
inline range2_s64 Range2S64(v2_s64 Min, v2_s64 Max);
inline b32 InRange(range2_s64 Range, v2_s64 Value);
inline v2_s64 DimOfRange(range2_s64 Range);
inline range2_s64 Intersection(range2_s64 A, range2_s64 B);
//- sixten: Shorthand base types

View File

@ -262,7 +262,7 @@ static string ConvertS64ToString(memory_arena *Arena, s64 Value)
Value = -Value;
}
s64 DigitCount = (s64)Floor(Log(Max(Value, 1)) / Log(10)) + 1;
s64 DigitCount = (s64)Floor(Log(Max(Value, 1LL)) / Log(10)) + 1;
s64 TotalBufferCount = DigitCount + IsNegative;

View File

@ -1,4 +1,4 @@
#include "vn_core.h"
#include "core/core.h"
#include <stdio.h>
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))

View File

@ -20,6 +20,8 @@ ui_style_stack UIStyleStacks[] =
{ "CornerRadius", "r32", "CornerRadius" },
{ "Font", "font_id", "Font" },
{ "FontSize", "r32", "FontSize" },
{ "OffsetX", "r32", "Offset.x" },
{ "OffsetY", "r32", "Offset.y" },
};
static void GenUI(void)

View File

@ -453,6 +453,76 @@ inline r32 UI_TopFontSize(void)
#define UI_FontSize(Element) DeferLoop(UI_PushFontSize(Element), UI_PopFontSize())
inline void UI_PushOffsetX(r32 Element)
{
ui *UI = UI_GetState();
Assert(UI->Stacks.OffsetXStackUsed + 1 < ArrayCount(UI->Stacks.OffsetXStack));
UI->Stacks.OffsetXStack[UI->Stacks.OffsetXStackUsed++] = Element;
}
inline void UI_PopOffsetX(void)
{
ui *UI = UI_GetState();
Assert(UI->Stacks.OffsetXStackUsed > 0);
--UI->Stacks.OffsetXStackUsed;
}
inline void UI_SetNextOffsetX(r32 Element)
{
ui *UI = UI_GetState();
UI_PushOffsetX(Element);
UI->Stacks.AutoPopOffsetX = true;
}
inline r32 UI_FirstOffsetX(void)
{
ui *UI = UI_GetState();
return(UI->Stacks.OffsetXStack[0]);
}
inline r32 UI_TopOffsetX(void)
{
ui *UI = UI_GetState();
return(UI->Stacks.OffsetXStack[UI->Stacks.OffsetXStackUsed - 1]);
}
#define UI_OffsetX(Element) DeferLoop(UI_PushOffsetX(Element), UI_PopOffsetX())
inline void UI_PushOffsetY(r32 Element)
{
ui *UI = UI_GetState();
Assert(UI->Stacks.OffsetYStackUsed + 1 < ArrayCount(UI->Stacks.OffsetYStack));
UI->Stacks.OffsetYStack[UI->Stacks.OffsetYStackUsed++] = Element;
}
inline void UI_PopOffsetY(void)
{
ui *UI = UI_GetState();
Assert(UI->Stacks.OffsetYStackUsed > 0);
--UI->Stacks.OffsetYStackUsed;
}
inline void UI_SetNextOffsetY(r32 Element)
{
ui *UI = UI_GetState();
UI_PushOffsetY(Element);
UI->Stacks.AutoPopOffsetY = true;
}
inline r32 UI_FirstOffsetY(void)
{
ui *UI = UI_GetState();
return(UI->Stacks.OffsetYStack[0]);
}
inline r32 UI_TopOffsetY(void)
{
ui *UI = UI_GetState();
return(UI->Stacks.OffsetYStack[UI->Stacks.OffsetYStackUsed - 1]);
}
#define UI_OffsetY(Element) DeferLoop(UI_PushOffsetY(Element), UI_PopOffsetY())
inline void UI_ApplyStyles(ui_box *Box)
{
@ -561,4 +631,20 @@ inline void UI_ApplyStyles(ui_box *Box)
UI->Stacks.AutoPopFontSize = false;
}
Assert(UI->Stacks.OffsetXStackUsed > 0);
Box->Offset.x = UI->Stacks.OffsetXStack[UI->Stacks.OffsetXStackUsed - 1];
if(UI->Stacks.AutoPopOffsetX)
{
UI_PopOffsetX();
UI->Stacks.AutoPopOffsetX = false;
}
Assert(UI->Stacks.OffsetYStackUsed > 0);
Box->Offset.y = UI->Stacks.OffsetYStack[UI->Stacks.OffsetYStackUsed - 1];
if(UI->Stacks.AutoPopOffsetY)
{
UI_PopOffsetY();
UI->Stacks.AutoPopOffsetY = false;
}
}

View File

@ -39,4 +39,10 @@ struct ui_style_stacks
r32 FontSizeStack[64];
s32 FontSizeStackUsed;
b32 AutoPopFontSize;
r32 OffsetXStack[64];
s32 OffsetXStackUsed;
b32 AutoPopOffsetX;
r32 OffsetYStack[64];
s32 OffsetYStackUsed;
b32 AutoPopOffsetY;
};

View File

@ -7,6 +7,7 @@ struct debug_settings
{
b32 RenderUIDebugRects;
b32 RenderFPSCounter;
b32 ListHotAndActive;
};
per_thread debug_settings *DEBUG_DebugSettings = 0;
@ -18,6 +19,7 @@ per_thread debug_settings *DEBUG_DebugSettings = 0;
#include "vn_font.h"
#include "vn_text_op.h"
#include "vn_ui.h"
#include "vn_ui_utils.h"
#include "vn_workspace.h"
#include "vn_theme_dark.h"
#include "vn_animation_curve.h"
@ -66,6 +68,7 @@ VN_UPDATE_AND_RENDER(VN_UpdateAndRender)
#if VN_INTERNAL
Config_BindB32(State->Config, StrLit("Dev/RenderUIDebugRects"), &State->DebugSettings.RenderUIDebugRects, 0);
Config_BindB32(State->Config, StrLit("Dev/RenderFPSCounter"), &State->DebugSettings.RenderFPSCounter, 0);
Config_BindB32(State->Config, StrLit("Dev/ListHotAndActive"), &State->DebugSettings.ListHotAndActive, 0);
#endif
Config_ReadFile(State->Config, StrLit("config.vn"));
@ -79,13 +82,13 @@ VN_UPDATE_AND_RENDER(VN_UpdateAndRender)
#endif
AnimationCurve_NewFrame(&State->AnimationCurveState, Input->dtForFrame);
UI_NewFrame(&State->UI, Input->EventList, Input->MouseP);
UI_NewFrame(&State->UI, Input->EventList, Input->MouseP, State->GlyphAtlas);
UI_BeginBuild(RenderCommands->RenderDim);
{
Workspace_Update(&State->Workspace, RenderCommands, Input, State->GlyphAtlas);
}
UI_EndBuild(State->GlyphAtlas);
UI_EndBuild();
for(platform_event *Event = Input->EventList->First;
@ -105,4 +108,12 @@ VN_UPDATE_AND_RENDER(VN_UpdateAndRender)
PushClear(&Group, V3(0.1, 0.1, 0.1));
UI_RenderFrame(&Group, State->GlyphAtlas);
if(DEBUG_DebugSettings->ListHotAndActive)
{
PushText(&Group, State->GlyphAtlas, Font_Regular, V2(5, RenderCommands->RenderDim.y - 20), 15, Color_Grey,
PushFormat(&State->UI.FrameArena, "Hot: %S:%llu", UI_GetBoxNameByKey(UI_GetHot()), UI_GetHot()));
PushText(&Group, State->GlyphAtlas, Font_Regular, V2(5, RenderCommands->RenderDim.y - 40), 15, Color_Grey,
PushFormat(&State->UI.FrameArena, "Active: %S:%llu", UI_GetBoxNameByKey(UI_GetActive()), UI_GetActive()));
}
}

View File

@ -116,6 +116,7 @@ static glyph_atlas *CreateGlyphAtlas(vn_render_commands *RenderCommands,
Atlas->Fonts[Font_Regular].Data = Platform_ReadEntireFile(&Atlas->Arena, StrLit("fonts/Roboto-Regular.ttf"));
Atlas->Fonts[Font_Bold].Data = Platform_ReadEntireFile(&Atlas->Arena, StrLit("fonts/Roboto-Bold.ttf"));
Atlas->Fonts[Font_Monospace].Data = Platform_ReadEntireFile(&Atlas->Arena, StrLit("fonts/Liberation-Mono.ttf"));
Atlas->Fonts[Font_Hand].Data = Platform_ReadEntireFile(&Atlas->Arena, StrLit("fonts/PatrickHand-Regular.ttf"));
Atlas->Fonts[Font_Icons].Data = Platform_ReadEntireFile(&Atlas->Arena, StrLit("fonts/icons.ttf"));
for(s32 FontIndex = 0;

View File

@ -8,6 +8,7 @@ enum font_id
Font_Regular,
Font_Bold,
Font_Monospace,
Font_Hand,
Font_Icons,
Font_Count,

View File

@ -127,7 +127,7 @@ inline void PushTexturedQuad(render_group *Group,
v2 DestMin = Max(Dest.Min, Clip.Min);
v2 DestMax = Min(Dest.Max, Clip.Max);
//if(InRange(Clip, P) || InRange(Clip, P + Dim))
if(InRange(Clip, P) || InRange(Clip, P + Dim))
{
v2 HalfSize = Dim*0.5;

View File

@ -8,6 +8,9 @@
#define ColorFromHex(Value) V4((((Value) >> 24) & 0xFF) / 255.0, (((Value) >> 16) & 0xFF) / 255.0, (((Value) >> 8) & 0xFF) / 255.0, (((Value) >> 0) & 0xFF) / 255.0)
#define Brighten(Color, Amount) (Color*(Amount))
#define Darken(Color, Amount) (Color*(1.0/(Amount)))
read_only v4 Color_Black = V4(0, 0, 0, 1);
read_only v4 Color_White = V4(1, 1, 1, 1);
read_only v4 Color_Grey = V4(0.5, 0.5, 0.5, 1);

View File

@ -30,7 +30,6 @@ inline ui_size UI_ChildrenSum(r32 Value, r32 Strictness)
return(Result);
}
per_thread ui *ThreadLocal_UI;
inline void UI_SetState(ui *UI)
@ -304,26 +303,36 @@ inline void UI_SetNextHot(ui_key Key)
}
}
inline b32 UI_ChildrenContainsP(ui_box *Box, v2 P)
// sixten(NOTE): ClippingRect = Intersection(TrueClippingRect, Parent->Rect);
static b32 UI_ChildrenContainsP(ui_box *Parent, v2 P, range2_r32 Clip)
{
b32 Result = false;
if(Box->Flags & UI_BoxFlag_Clickable && InRange(Box->Rect, P))
for(ui_box *Child = Parent->First;
Child != 0;
Child = Child->Next)
{
range2_r32 IntersectionRect = Intersection(Clip, Child->Rect);
if(Child->Flags & UI_BoxFlag_Clickable && InRange(IntersectionRect, P))
{
Result = true;
break;
}
else
{
if(Box->First)
if(Child->Flags & UI_BoxFlag_Clip)
{
Result = UI_ChildrenContainsP(Box->First, P);
Result = UI_ChildrenContainsP(Child, P, IntersectionRect);
}
else
{
Result = UI_ChildrenContainsP(Child, P, Clip);
}
if(!Result)
if(Result)
{
if(Box->Next)
{
Result = UI_ChildrenContainsP(Box->Next, P);
break;
}
}
}
@ -337,9 +346,19 @@ static ui_signal UI_SignalFromBox(ui_box *Box)
ui_signal Signal = {};
Signal.Hovering = InRange(Box->Rect, UI->MouseP) &&
!(Box->First && UI_ChildrenContainsP(Box->First, UI->MouseP)) &&
!(Box->Next && UI_ChildrenContainsP(Box->Next, UI->MouseP));
// sixten: Gather the clipping rects of all the parents.
range2_r32 ClippedRect = Box->Rect;
for(ui_box *Parent = Box->Parent;
Parent != 0;
Parent = Parent->Parent)
{
if(Parent->Flags & UI_BoxFlag_Clip)
{
ClippedRect = Intersection(ClippedRect, Parent->Rect);
}
}
Signal.Hovering = InRange(ClippedRect, UI->MouseP) && !UI_ChildrenContainsP(Box, UI->MouseP, ClippedRect);
// sixten: Make sure the tooltip is not overlapping.
{
@ -356,9 +375,10 @@ static ui_signal UI_SignalFromBox(ui_box *Box)
}
}
if(!FoundTooltip && UI->TooltipNode->First)
if(!FoundTooltip)
{
Signal.Hovering &= ~UI_ChildrenContainsP(UI->TooltipNode->First, UI->MouseP);
Signal.Hovering &= ~UI_ChildrenContainsP(UI->TooltipNode, UI->MouseP,
Range2R32(V2(0, 0), V2(InfinityR32, InfinityR32)));
}
}
@ -585,7 +605,8 @@ static void UI_DrawBox(ui_box *Box, render_group *Group, glyph_atlas *GlyphAtlas
if(Box->Flags & UI_BoxFlag_Clip)
{
PushClip(Group, Box->Rect);
range2_r32 Rect = Intersection(Group->ClipStack[Group->ClipStackUsed], Box->Rect);
PushClip(Group, Rect);
}
for(ui_box *Child = Box->First;
@ -622,12 +643,13 @@ static void UI_DrawBox(ui_box *Box, render_group *Group, glyph_atlas *GlyphAtlas
}
}
static r32 UI_CalculateChildrenSum(ui_box *Box, axis2 Axis, glyph_atlas *Atlas);
static r32 UI_CalculateChildrenSum(ui_box *Box, axis2 Axis);
static r32 UI_CalculateBoxSize(ui_box *Box, axis2 Axis, glyph_atlas *Atlas)
static r32 UI_CalculateBoxSize(ui_box *Box, axis2 Axis)
{
r32 Result = 0;
ui *UI = UI_GetState();
ui_box *Parent = Box->Parent;
switch(Box->SemanticSize[Axis].Type)
@ -639,6 +661,8 @@ static r32 UI_CalculateBoxSize(ui_box *Box, axis2 Axis, glyph_atlas *Atlas)
case UI_SizeType_TextContent:
{
glyph_atlas *Atlas = UI->GlyphAtlas;
Result = ((Axis == Axis2_X) ?
CalculateRasterizedTextWidth(Atlas, Box->Font, Box->FontSize, Box->String) :
CalculateRasterizedTextHeight(Atlas, Box->Font, Box->FontSize, Box->String)) +
@ -661,7 +685,7 @@ static r32 UI_CalculateBoxSize(ui_box *Box, axis2 Axis, glyph_atlas *Atlas)
case UI_SizeType_ChildrenSum:
{
Result = UI_CalculateChildrenSum(Box, Axis, Atlas)*Box->SemanticSize[Axis].Value;
Result = UI_CalculateChildrenSum(Box, Axis)*Box->SemanticSize[Axis].Value;
} break;
InvalidDefaultCase;
@ -670,7 +694,7 @@ static r32 UI_CalculateBoxSize(ui_box *Box, axis2 Axis, glyph_atlas *Atlas)
return(Result);
}
static r32 UI_CalculateChildrenSum(ui_box *Box, axis2 Axis, glyph_atlas *Atlas)
static r32 UI_CalculateChildrenSum(ui_box *Box, axis2 Axis)
{
r32 Result = 0;
@ -680,7 +704,7 @@ static r32 UI_CalculateChildrenSum(ui_box *Box, axis2 Axis, glyph_atlas *Atlas)
Child != 0;
Child = Child->Next)
{
Result += UI_CalculateBoxSize(Child, Axis, Atlas);
Result += UI_CalculateBoxSize(Child, Axis);
}
}
else
@ -689,31 +713,33 @@ static r32 UI_CalculateChildrenSum(ui_box *Box, axis2 Axis, glyph_atlas *Atlas)
Child != 0;
Child = Child->Next)
{
Result = Max(Result, UI_CalculateBoxSize(Child, Axis, Atlas));
Result = Max(Result, UI_CalculateBoxSize(Child, Axis));
}
}
return(Result);
}
static void UI_LayoutBox(ui_box *Box, axis2 Axis, glyph_atlas *Atlas)
static void UI_LayoutBox(ui_box *Box)
{
for(ui_box *Child = Box->First;
Child != 0;
Child = Child->Next)
{
Child->ComputedDim.E[Axis] = UI_CalculateBoxSize(Child, Axis, Atlas);
Child->ComputedDim.E[Axis2_X] = UI_CalculateBoxSize(Child, Axis2_X);
Child->ComputedDim.E[Axis2_Y] = UI_CalculateBoxSize(Child, Axis2_Y);
}
UI_SolveSizeViolations(Box, Axis);
UI_SolveSizeViolations(Box, Axis2_X);
UI_SolveSizeViolations(Box, Axis2_Y);
for(ui_box *Child = Box->First;
Child;
Child != 0;
Child = Child->Next)
{
Child->Rect.Min.E[Axis] = Box->Rect.Min.E[Axis] + Child->ComputedRelativeP.E[Axis];
Child->Rect.Max.E[Axis] = Child->Rect.Min.E[Axis] + Child->ComputedDim.E[Axis];
Child->Rect.Min = Box->Rect.Min + Child->ComputedRelativeP + Box->Offset;
Child->Rect.Max = Child->Rect.Min + Child->ComputedDim;
UI_LayoutBox(Child, Axis, Atlas);
UI_LayoutBox(Child);
}
}
@ -734,6 +760,8 @@ static void UI_BeginBuild(v2 ScreenDim)
UI_PushCornerRadius(0);
UI_PushFont(Font_Regular);
UI_PushFontSize(15.0f);
UI_PushOffsetX(0);
UI_PushOffsetY(0);
UI->RootNode = UI_MakeBox(UI_BoxFlag_DrawBackground, StrLit("UI Root Node"));
UI->Stacks.ParentStack[0] = UI->RootNode;
@ -746,7 +774,7 @@ static void UI_BeginBuild(v2 ScreenDim)
UI->NextHotSet = false;
}
static void UI_EndBuild(glyph_atlas *GlyphAtlas)
static void UI_EndBuild(void)
{
ui *UI = UI_GetState();
@ -770,9 +798,10 @@ static void UI_EndBuild(glyph_atlas *GlyphAtlas)
UI_PopCornerRadius();
UI_PopFont();
UI_PopFontSize();
UI_PopOffsetX();
UI_PopOffsetY();
UI_LayoutBox(UI->RootNode, Axis2_X, GlyphAtlas);
UI_LayoutBox(UI->RootNode, Axis2_Y, GlyphAtlas);
UI_LayoutBox(UI->RootNode);
}
static void UI_RenderFrame(render_group *Group, glyph_atlas *GlyphAtlas)
@ -804,7 +833,7 @@ inline void UI_ScanForHotAndActive(ui_box *Box, b32 *FoundHot, b32 *FoundActive)
}
}
static void UI_NewFrame(ui *UI, platform_event_list *EventList, v2 MouseP)
static void UI_NewFrame(ui *UI, platform_event_list *EventList, v2 MouseP, glyph_atlas *GlyphAtlas)
{
UI_SetState(UI);
@ -818,6 +847,8 @@ static void UI_NewFrame(ui *UI, platform_event_list *EventList, v2 MouseP)
UI->EventList = EventList;
UI->MouseP = MouseP;
UI->GlyphAtlas = GlyphAtlas;
// sixten: Make sure that the hot and active boxes are valid.
if(UI->RootNode)
{

View File

@ -3,6 +3,7 @@
#ifndef VN_UI_H
#define VN_UI_H
//- sixten: Keying
struct ui_key
{
u64 Value;
@ -14,23 +15,7 @@ inline b32 AreEqual(ui_key A, ui_key B)
return(Result);
}
enum
{
UI_BoxFlag_Clickable = (1 << 0),
UI_BoxFlag_DrawText = (1 << 1),
UI_BoxFlag_DrawBorder = (1 << 2),
UI_BoxFlag_DrawBackground = (1 << 3),
UI_BoxFlag_DrawDropShadow = (1 << 4),
UI_BoxFlag_Clip = (1 << 5),
UI_BoxFlag_HotAnimation = (1 << 6),
UI_BoxFlag_ActiveAnimation = (1 << 7),
UI_BoxFlag_OverflowX = (1 << 8),
UI_BoxFlag_OverflowY = (1 << 9),
UI_BoxFlag_FloatingX = (1 << 10),
UI_BoxFlag_FloatingY = (1 << 11),
};
typedef u32 ui_box_flags;
//- sixten: Semantic sizing
enum ui_size_type
{
UI_SizeType_Pixels,
@ -52,6 +37,24 @@ inline ui_size UI_TextContent(r32 Value, r32 Strictness);
inline ui_size UI_Percent(r32 Value, r32 Strictness);
inline ui_size UI_ChildrenSum(r32 Value, r32 Strictness);
//- sixten: UI core
enum
{
UI_BoxFlag_Clickable = (1 << 0),
UI_BoxFlag_DrawText = (1 << 1),
UI_BoxFlag_DrawBorder = (1 << 2),
UI_BoxFlag_DrawBackground = (1 << 3),
UI_BoxFlag_DrawDropShadow = (1 << 4),
UI_BoxFlag_Clip = (1 << 5),
UI_BoxFlag_HotAnimation = (1 << 6),
UI_BoxFlag_ActiveAnimation = (1 << 7),
UI_BoxFlag_OverflowX = (1 << 8),
UI_BoxFlag_OverflowY = (1 << 9),
UI_BoxFlag_FloatingX = (1 << 10),
UI_BoxFlag_FloatingY = (1 << 11),
};
typedef u32 ui_box_flags;
typedef void ui_draw_callback(render_group *Group, glyph_atlas *Atlas, struct ui_box *Box, void *Data);
struct ui_box
@ -81,6 +84,7 @@ struct ui_box
r32 CornerRadius;
font_id Font;
r32 FontSize;
v2 Offset;
ui_draw_callback *DrawCallback;
void *DrawCallbackData;
@ -136,13 +140,15 @@ struct ui
ui_key NextHot;
b32 NextHotSet;
platform_event_list *EventList;
v2 MouseP;
u64 DragData[8];
v2 DragStartP;
ui_style_stacks Stacks;
platform_event_list *EventList;
v2 MouseP;
glyph_atlas *GlyphAtlas;
};
//- sixten: State management
@ -166,20 +172,23 @@ inline void UI_StoreDragPointer(void *Data);
inline void *UI_GetDragDataPointer(void);
//- sixten: Key functions
inline ui_key UI_EmptyKey(void);
inline ui_key UI_SeedKey(ui_key Key, ui_key Seed);
inline ui_key UI_GenerateKeyFromString(string String);
static ui_key UI_EmptyKey(void);
static ui_key UI_SeedKey(ui_key Key, ui_key Seed);
static ui_key UI_GenerateKeyFromString(string String);
static string UI_GetBoxNameByKey(ui_key Key);
inline ui_box *UI_GetBoxByKey(ui *UI, ui_key Key);
static ui_box *UI_GetBoxByKey(ui *UI, ui_key Key);
//- sixten: Box creation
inline ui_box *UI_MakeBox(ui_box_flags Flags, string String);
inline ui_box *UI_MakeBoxF(ui_box_flags Flags, char *Format, ...);
static ui_box *UI_MakeBox(ui_box_flags Flags, string String);
static ui_box *UI_MakeBoxF(ui_box_flags Flags, char *Format, ...);
//- sixten: User interaction
static ui_signal UI_SignalFromBox(ui_box *Box);
//- sixten: Building and rendering
static void UI_BeginBuild(ui *UI, v2 ScreenDim);
static void UI_EndBuild(glyph_atlas *GlyphAtlas);
static void UI_RenderFrame(render_group *RenderGroup, glyph_atlas *GlyphAtlas);
static void UI_NewFrame(ui *UI, platform_event_list *EventList, v2 MouseP);
static void UI_EndBuild(void);
static void UI_RenderFrame(render_group *RenderGroup);
static void UI_NewFrame(ui *UI, platform_event_list *EventList, v2 MouseP, glyph_atlas *GlyphAtlas);
#endif //VN_UI_H

View File

@ -1,8 +1,8 @@
// sixten: Rows and columns.
inline void UI_RowBegin(void)
//- sixten: Rows and columns.
inline void UI_RowBegin(u32 Flags, string Name)
{
UI_SetNextLayoutAxis(Axis2_X);
ui_box *Box = UI_MakeBox(0, StrLit(""));
ui_box *Box = UI_MakeBox(Flags, Name);
UI_PushParent(Box);
}
@ -11,10 +11,10 @@ inline void UI_RowEnd(void)
UI_PopParent();
}
inline void UI_ColumnBegin(void)
inline void UI_ColumnBegin(u32 Flags, string Name)
{
UI_SetNextLayoutAxis(Axis2_Y);
ui_box *Box = UI_MakeBox(0, StrLit(""));
ui_box *Box = UI_MakeBox(Flags, Name);
UI_PushParent(Box);
}
@ -23,10 +23,7 @@ inline void UI_ColumnEnd(void)
UI_PopParent();
}
#define UI_Row DeferLoop(UI_RowBegin(), UI_RowEnd())
#define UI_Column DeferLoop(UI_ColumnBegin(), UI_ColumnEnd())
// sixten: Compositions
//- sixten: Compositions
inline void UI_PushAxisSize(axis2 Axis, ui_size Size)
{
if(Axis == Axis2_X)
@ -63,17 +60,7 @@ inline void UI_SetNextAxisSize(axis2 Axis, ui_size Size)
}
}
#define UI_AxisSize(Axis, Size) DeferLoop(UI_PushAxisSize(Axis, Size), UI_PopAxisSize(Axis))
#define UI_Size(Width, Height) UI_Width(Width) UI_Height(Height)
#define UI_PushSize(Width, Height) UI_PushWidth(Width); UI_PushHeight(Height)
#define UI_PopSize() UI_PopWidth(); UI_PopHeight()
#define UI_SetNextSize(Width, Height) UI_SetNextWidth(Width); UI_SetNextHeight(Height)
#define UI_FixedP(Value) UI_FixedX(Value.x) UI_FixedY(Value.y)
#define UI_SetNextFixedP(Value) UI_SetNextFixedX(Value.x); UI_SetNextFixedY(Value.y)
// sixten: Spacing
//- sixten: Spacing
static ui_box *UI_NamedSpacer(ui_size Size, string String)
{
ui_box *Parent = UI_TopParent();
@ -104,9 +91,10 @@ static void UI_Spacer(ui_size Size)
UI_NamedSpacer(Size, StrLit(""));
}
#define UI_Padding(Size) DeferLoop(UI_Spacer(Size), UI_Spacer(Size))
//- sixten: Scrollable regions
// sixten: Common widgets
//- sixten: Common widgets
static ui_box *UI_Label(string String)
{
ui_box *Box = UI_MakeBox(UI_BoxFlag_DrawText, String);
@ -165,7 +153,7 @@ static ui_signal UI_ButtonF(char *Format, ...)
return(Signal);
}
static ui_signal UI_Checkbox(string String, b32 *Checked)
static ui_signal UI_Checkbox(b32 *Checked, string String)
{
UI_SetNextSize(UI_ChildrenSum(1, 1), UI_ChildrenSum(1, 1));
UI_SetNextLayoutAxis(Axis2_X);
@ -197,3 +185,173 @@ static ui_signal UI_Checkbox(string String, b32 *Checked)
return(Signal);
}
//- sixten: Scrollable regions
static ui_signal UI_Scrollbar(axis2 Axis, string Name, r32 Size, r32 Offset)
{
ui_signal Signal;
UI_SetNextAxisSize(Axis, UI_Percent(1, 0));
UI_SetNextAxisSize(Opposite(Axis), UI_Em(1, 1));
UI_SetNextBackgroundColor(Darken(UI_TopBackgroundColor(), 2));
UI_SetNextLayoutAxis(Axis);
UI_Parent(UI_MakeBox(UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawBackground, Name))
{
UI_SetNextSize(UI_Em(1, 1), UI_Em(1, 1));
UI_SetNextFont(Font_Icons);
UI_MakeBoxF(UI_BoxFlag_DrawText, "%U", Axis?FontIcon_UpDir:FontIcon_LeftDir);
UI_Spacer(UI_Pixels(Offset, 1));
UI_SetNextAxisSize(Axis, UI_Pixels(Size, 1));
UI_SetNextAxisSize(Opposite(Axis), UI_Percent(1, 1));
Signal = UI_SignalFromBox(UI_MakeBox(UI_BoxFlag_DrawBorder |
UI_BoxFlag_DrawBackground |
UI_BoxFlag_Clickable |
UI_BoxFlag_HotAnimation |
UI_BoxFlag_ActiveAnimation,
StrLit("Slider")));
UI_Spacer(UI_Percent(1, 0));
UI_SetNextSize(UI_Em(1, 1), UI_Em(1, 1));
UI_SetNextFont(Font_Icons);
UI_MakeBoxF(UI_BoxFlag_DrawText, "%U", Axis?FontIcon_DownDir:FontIcon_RightDir);
}
return(Signal);
}
/*
Goal for next week: Be able to make basic dialog trees.
Goal for tomorrow: Scrollable regions and basic text editing (with line wrapping).
*/
static void UI_ScrollBegin(r32 *X, r32 *Y, string Name)
{
u32 Flags = 0;
b32 AllowOnX = (X != 0);
b32 AllowOnY = (Y != 0);
UI_RowBegin(UI_BoxFlag_Clip|UI_BoxFlag_DrawBorder, Name);
{
UI_SetNextSize(UI_Percent(1, 0), UI_Percent(1, 0));
UI_ColumnBegin();
{
if(AllowOnX)
{
Flags |= UI_BoxFlag_OverflowX;
UI_SetNextOffsetX(-(*X));
}
if(AllowOnY)
{
Flags |= UI_BoxFlag_OverflowY;
UI_SetNextOffsetY(-(*Y));
}
UI_SetNextSize(AllowOnX?UI_ChildrenSum(1, 1):UI_Percent(1, 0),
AllowOnY?UI_ChildrenSum(1, 1):UI_Percent(1, 0));
ui_box *ScrollableBox = UI_MakeBox(Flags, StrLit("Scrollable Box"));
UI_PushParent(ScrollableBox);
}
}
}
static void UI_ScrollEnd(r32 *X, r32 *Y)
{
b32 AllowOnX = (X != 0);
b32 AllowOnY = (Y != 0);
ui_box *ScrollableBox = UI_TopParent();
{
{
UI_PopParent();
if(AllowOnX)
{
r32 TotalWidth = UI_CalculateBoxSize(ScrollableBox, Axis2_X);
r32 ViewWidth = UI_CalculateBoxSize(ScrollableBox->Parent->Parent, Axis2_X);
if(ViewWidth / TotalWidth < 1)
{
r32 TotalScrollWidth = ViewWidth - ScrollableBox->FontSize*2;
r32 ScrollScale = TotalScrollWidth / TotalWidth;
// sixten(TODO): Add a max of 1 em, and figure out the associated algebra.
r32 ScrollWidth = ViewWidth*ScrollScale;
r32 ScrollOffset = (*X)*ScrollScale;
ui_signal Signal = UI_Scrollbar(Axis2_X, StrLit("Scroll X"), ScrollWidth, ScrollOffset);
{
if(Signal.Dragging)
{
if(Signal.Pressed)
{
UI_StoreDragR32(*X);
}
r32 StartOffset = UI_GetDragR32();
r32 EndOffset = StartOffset + Signal.DragDelta.x/ScrollScale;
AnimationCurve_AnimateValueDirect(EndOffset, 0.2, X);
}
}
}
*X = Clamp(*X, 0, Max(0.0, TotalWidth - ViewWidth));
}
}
UI_ColumnEnd();
if(AllowOnY)
{
UI_SetNextSize(UI_ChildrenSum(1, 1), UI_Percent(1, 0));
UI_Column()
{
r32 TotalHeight = UI_CalculateBoxSize(ScrollableBox, Axis2_Y);
r32 ViewHeight = UI_CalculateBoxSize(ScrollableBox->Parent->Parent, Axis2_Y);
if(ViewHeight / TotalHeight < 1)
{
r32 TotalScrollHeight = ViewHeight - ScrollableBox->FontSize*2;
r32 ScrollScale = TotalScrollHeight / TotalHeight;
// sixten(TODO): Add a max of 1 em, and figure out the associated algebra.
r32 ScrollHeight = ViewHeight*ScrollScale;
r32 ScrollOffset = (*Y)*ScrollScale;
ui_signal Signal = UI_Scrollbar(Axis2_Y, StrLit("Scroll Y"), ScrollHeight, ScrollOffset);
{
if(Signal.Dragging)
{
if(Signal.Pressed)
{
UI_StoreDragR32(*Y);
}
r32 StartOffset = UI_GetDragR32();
r32 EndOffset = StartOffset + Signal.DragDelta.y/ScrollScale;
AnimationCurve_AnimateValueDirect(EndOffset, 0.2, Y);
}
}
}
*Y = Clamp(*Y, 0, Max(0.0, TotalHeight - ViewHeight));
}
// sixten: Add padding
if(AllowOnX && AllowOnY)
{
UI_SetNextSize(UI_Em(1, 1), UI_Em(1, 1));
UI_SetNextBackgroundColor(Darken(UI_TopBackgroundColor(), 2));
UI_MakeBox(UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawBackground, StrLit("Padding"));
}
}
}
UI_RowEnd();
}

56
code/vn_ui_utils.h 100644
View File

@ -0,0 +1,56 @@
/* date = June 24th 2023 9:43 pm */
#ifndef VN_UI_UTILS_H
#define VN_UI_UTILS_H
//- sixten: Rows and columns.
inline void UI_RowBegin(u32 Flags = 0, string Name = StrLit(""));
inline void UI_RowEnd(void);
inline void UI_ColumnBegin(u32 Flags = 0, string Name = StrLit(""));
inline void UI_ColumnEnd(void);
#define UI_Row(...) DeferLoop(UI_RowBegin(__VA_ARGS__), UI_RowEnd())
#define UI_Column(...) DeferLoop(UI_ColumnBegin(__VA_ARGS__), UI_ColumnEnd())
//- sixten: Compositions
inline void UI_PushAxisSize(axis2 Axis, ui_size Size);
inline void UI_PopAxisSize(axis2 Axis);
inline void UI_SetNextAxisSize(axis2 Axis, ui_size Size);
#define UI_AxisSize(Axis, Size) DeferLoop(UI_PushAxisSize(Axis, Size), UI_PopAxisSize(Axis))
#define UI_Size(Width, Height) UI_Width(Width) UI_Height(Height)
#define UI_PushSize(Width, Height) UI_PushWidth(Width); UI_PushHeight(Height)
#define UI_PopSize() UI_PopWidth(); UI_PopHeight()
#define UI_SetNextSize(Width, Height) UI_SetNextWidth(Width); UI_SetNextHeight(Height)
#define UI_Offset(x, y) UI_OffsetX(x) UI_OffsetY(y)
#define UI_PushOffset(x, y) UI_PushOffsetX(x); UI_PushOffsetY(y)
#define UI_PopOffset() UI_PopOffsetX(); UI_PopOffsetY()
#define UI_SetNextOffset(x, y) UI_SetNextOffsetX(x); UI_SetNextOffsetY(y)
#define UI_FixedP(Value) UI_FixedX(Value.x) UI_FixedY(Value.y)
#define UI_SetNextFixedP(Value) UI_SetNextFixedX(Value.x); UI_SetNextFixedY(Value.y)
//- sixten: Spacing
static ui_box *UI_NamedSpacer(ui_size Size, string String);
static ui_box *UI_NamedSpacerF(ui_size Size, char *Format, ...);
#define UI_Padding(Size) DeferLoop(UI_Spacer(Size), UI_Spacer(Size))
//- sixten: Scrollable regions
static void UI_ScrollBegin(r32 *X, r32 *Y, string Name = StrLit("Scrollable Region Container"));
static void UI_ScrollEnd(r32 *X, r32 *Y, string Name);
#define UI_Scroll(...) DeferLoop(UI_ScrollBegin(__VA_ARGS__), UI_ScrollEnd(__VA_ARGS__))
//- sixten: Common widgets
static ui_box *UI_Label(string String);
static ui_box *UI_LabelF(char *Format, ...);
static ui_signal UI_Button(string String);
static ui_signal UI_ButtonF(char *Format, ...);
static ui_signal UI_Checkbox(b32 *Checked, string String);
#endif //VN_UI_UTILS_H

View File

@ -205,6 +205,12 @@ static void Workspace_BuildToolbar(workspace *Workspace, r32 dtForFrame)
Workspace->Menu = ToolbarMenu_None;
}
if(Workspace_BuildMenuItem(FontIcon_None, "Command Palette", "").Clicked)
{
Workspace_CreateNewView(Workspace_View_CommandPalette, CurrentPanel);
Workspace->Menu = ToolbarMenu_None;
}
if(Workspace_BuildMenuItem(FontIcon_Wrench, "Settings", "").Clicked)
{
Workspace_CreateNewView(Workspace_View_Settings, CurrentPanel);
@ -419,6 +425,8 @@ static void Workspace_BuildPanelHeader(workspace *Workspace, workspace_panel *Pa
Workspace_BuildTabItem(Workspace, Panel, View);
}
UI_Spacer(UI_Percent(1, 0));
// sixten: Panel Close Button
if(Panel != Workspace->RootPanel)
{
@ -500,8 +508,8 @@ static void Workspace_BuildPanel(workspace *Workspace, workspace_panel *Panel)
}
else
{
UI_Column UI_Padding(UI_Percent(1, 0))
UI_Height(UI_ChildrenSum(1, 1)) UI_Row UI_Padding(UI_Percent(1, 0))
UI_Column() UI_Padding(UI_Percent(1, 0))
UI_Height(UI_ChildrenSum(1, 1)) UI_Row() UI_Padding(UI_Percent(1, 0))
{
UI_Size(UI_TextContent(0, 1), UI_TextContent(10, 1))
{

View File

@ -146,7 +146,7 @@ static void Workspace_BuildViewTypeLister(workspace *Workspace, workspace_view *
ui_box *InputTextBox = UI_MakeBox(UI_BoxFlag_DrawText, StrLit("Workspace View Lister"));
InputTextBox->String = MakeString(CommandPalette->ListerInput, CommandPalette->ListerInputUsed);
InputTextBox->DrawCallback = Workspace_ViewListerInputCallback;
InputTextBox->DrawCallbackData = View;
InputTextBox->DrawCallbackData = CommandPalette;
}
UI_Spacer(UI_Pixels(4, 1));
@ -338,8 +338,8 @@ static void Workspace_BuildSettings(workspace *Workspace, workspace_view *View)
workspace_view_settings *Settings = (workspace_view_settings *)View->Data;
UI_Height(UI_ChildrenSum(1, 1))
UI_Column UI_Padding(UI_Pixels(50, 0))
UI_Row UI_Padding(UI_Pixels(50, 0))
UI_Column() UI_Padding(UI_Pixels(50, 0))
UI_Row() UI_Padding(UI_Pixels(50, 0))
{
UI_Size(UI_TextContent(0, 1), UI_TextContent(10, 1))
UI_Font(Font_Bold) UI_FontSize(36)
@ -353,8 +353,8 @@ static void Workspace_BuildSettings(workspace *Workspace, workspace_view *View)
UI_Width(UI_Pixels(300, 1))
UI_Parent(UI_MakeBoxF(0, "Navigation"))
{
UI_Row UI_Padding(UI_Pixels(50, 1))
UI_Width(UI_Percent(1, 0)) UI_Column UI_Padding(UI_Percent(1, 0))
UI_Row() UI_Padding(UI_Pixels(50, 1))
UI_Width(UI_Percent(1, 0)) UI_Column() UI_Padding(UI_Percent(1, 0))
UI_Height(UI_ChildrenSum(1, 1)) UI_LayoutAxis(Axis2_Y) UI_Parent(UI_MakeBoxF(0, ""))
{
Workspace_BuildSettingsTabButton(Settings, "All", Workspace_Settings_All);
@ -391,12 +391,11 @@ static void Workspace_BuildSettings(workspace *Workspace, workspace_view *View)
s32 DropdownSelected;
FindIndexOfElement(DropdownSelected, AlternativeMapping, 0, Workspace->Input->RefreshRate);
persist b32 DropdownOpen = false;
if(UI_DropdownSelection(Alternatives, ArrayCount(Alternatives), &DropdownOpen, &DropdownSelected))
if(UI_DropdownSelection(Alternatives, ArrayCount(Alternatives),
&Settings->GeneralDropdownOpen, &DropdownSelected))
{
Workspace->Input->RefreshRate = AlternativeMapping[DropdownSelected];
DropdownOpen = false;
Settings->GeneralDropdownOpen = false;
}
UI_Spacer(UI_Pixels(50, 1));
@ -406,19 +405,25 @@ static void Workspace_BuildSettings(workspace *Workspace, workspace_view *View)
{
UI_Font(Font_Bold) UI_FontSize(36) UI_LabelF("Theme");
UI_SetNextSize(UI_Percent(1, 1), UI_ChildrenSum(1, 1));
UI_SetNextCornerRadius(3);
UI_SetNextSize(UI_Percent(1, 1), UI_Em(13, 1));
UI_Parent(UI_MakeBoxF(UI_BoxFlag_DrawBorder, "Theme Lister"))
UI_Scroll(0, &Settings->ThemeScroll)
{
UI_Size(UI_Percent(1, 1), UI_Em(2, 1))
{
UI_ButtonF("Hello");
UI_ButtonF("Line");
UI_ButtonF("Paint");
UI_ButtonF("Color");
UI_ButtonF("Design");
UI_ButtonF("Address");
UI_ButtonF("Brightness");
for(s32 Index = 0;
Index < 2;
++Index)
{
UI_ButtonF("Hello#%i", Index);
UI_ButtonF("Line#%i", Index);
UI_ButtonF("Paint#%i", Index);
UI_ButtonF("Color#%i", Index);
UI_ButtonF("Design#%i", Index);
UI_ButtonF("Address#%i", Index);
UI_ButtonF("Brightness#%i", Index);
}
}
}
UI_Spacer(UI_Pixels(50, 1));
@ -427,9 +432,11 @@ static void Workspace_BuildSettings(workspace *Workspace, workspace_view *View)
if(!Category || (Category == Workspace_Settings_Developer))
{
UI_Font(Font_Bold) UI_FontSize(36) UI_LabelF("Developer");
UI_Checkbox(StrLit("Render UI Debug Rects"), &DEBUG_DebugSettings->RenderUIDebugRects);
UI_Checkbox(&DEBUG_DebugSettings->RenderUIDebugRects, StrLit("Render UI Debug Rects"));
UI_Spacer(UI_Pixels(5, 1));
UI_Checkbox(StrLit("Render FPS Counter"), &DEBUG_DebugSettings->RenderFPSCounter);
UI_Checkbox(&DEBUG_DebugSettings->RenderFPSCounter, StrLit("Render FPS Counter"));
UI_Spacer(UI_Pixels(5, 1));
UI_Checkbox(&DEBUG_DebugSettings->ListHotAndActive, StrLit("List Hot & Active"));
UI_Spacer(UI_Pixels(50, 1));
}
@ -459,8 +466,8 @@ static void Workspace_BuildView(workspace *Workspace, workspace_view *View)
{
if(View->Type == Workspace_View_Startup)
{
UI_Row UI_Padding(UI_Pixels(50, 0))
UI_Width(UI_ChildrenSum(1, 1)) UI_Column UI_Padding(UI_Pixels(50, 0))
UI_Row() UI_Padding(UI_Pixels(50, 0))
UI_Width(UI_ChildrenSum(1, 1)) UI_Column() UI_Padding(UI_Pixels(50, 0))
{
UI_Size(UI_TextContent(0, 1), UI_TextContent(10, 1))
{

View File

@ -53,6 +53,12 @@ enum workspace_settings_category
struct workspace_view_settings
{
workspace_settings_category Category;
// sixten: General
b32 GeneralDropdownOpen;
// sixten: Theme
r32 ThemeScroll;
};
//- sixten: Views

View File

@ -7,4 +7,5 @@ Dev
{
RenderUIDebugRects = false;
RenderFPSCounter = false;
ListHotAndActive = true;
}

Binary file not shown.

View File

@ -1,9 +1,12 @@
This is a list of things that needs doing in a SUGGESTED order.
* UI
- Settings / Preferences view. (Including saving and loading of these settings/preferences)
- Incorrect behaviour when closing panels in a certain order, most likely some things are not being copied over properly.
* Rendering
- Fix texture clipping
- Control over each corner when rounding
Completed
* UI
- Settings / Preferences view. (Including saving and loading of these settings/preferences)