Init new repository

This commit is contained in:
Krzosa Karol
2024-04-13 15:29:53 +02:00
commit 5a2e3dcec4
335 changed files with 61571 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
import "shared";
IO_Assertf :: proc(b: bool, s: *char, ...); @foreign
IO_Assert :: proc(b: bool); @foreign
IO_InvalidCodepath :: proc(); @foreign
S8_Lit :: proc(s: *char): S8_String; @foreign
IO_FatalErrorf :: proc(msg: *char, ...); @foreign
IO_FatalError :: proc(msg: *char); @foreign
HashBytes :: proc(data: *void, data_size: u64): u64; @foreign
// @todo: hmm ?
SLL_QUEUE_ADD :: proc(first: *void, last: *void, data: *void); @foreign
Mu: *MU_Context;
Gs: *GameState;
Perm: *MA_Arena;
Temp: *MA_Arena;
R: *R_Render;
GameState :: struct {
render: R_Render;
}
activated: bool;
APP_Update :: proc(event: LibraryEvent, mu: *MU_Context, dll_context: *DLL_Context) {
Mu = mu;
Perm = &dll_context.perm;
Temp = dll_context.temp;
Gs = dll_context.context;
if event == Init {
dll_context.context = MA_PushSize(Perm, :usize(sizeof(:GameState)));
Gs = dll_context.context;
R_Init(&Gs.render);
return;
}
if event == Reload {
R_Reload();
return;
}
if event == Unload {
return;
}
UI_Begin();
Ui.cut = UI_Cut_Top;
UI_PushLayout({});
{
Ui.cut = UI_Cut_Left;
if (UI_Checkbox(&activated, "File")) {
}
UI_Button("Edit");
Ui.cut = UI_Cut_Right;
UI_Button("Memes");
Ui.s.cut_size.x += 100000;
UI_Fill();
UI_PopLayout();
UI_PopStyle();
}
UI_End();
R_Text2D({
pos = {100, 100},
text = S8_Lit("Testing memes"),
color = R_ColorWhite,
do_draw = true,
scale = 1.0,
});
R_Rect2D(R2P_SizeF(200, 200, 100, 100), R.atlas.white_texture_bounding_box, R_ColorWhite);
R_EndFrame();
}

View File

@@ -0,0 +1,122 @@
V2 :: struct { x: f32; y: f32; }
V3 :: struct { x: f32; y: f32; z: f32; }
V4 :: struct { x: f32; y: f32; z: f32; w: f32; }
R2P :: struct { min: V2; max: V2; }
V2I :: struct { x: i32; y: i32; }
V3I :: struct { x: i32; y: i32; z: i32; }
V4I :: struct { x: i32; y: i32; z: i32; w: i32; }
R2P_SizeF :: proc(px: f32, py: f32, sx: f32, sy: f32): R2P {
result: R2P = {{px, py}, {px + sx, py + sy}};
return result;
}
R2P_Size :: proc(pos: V2, size: V2): R2P {
result := :R2P{{pos.x, pos.y}, {pos.x + size.x, pos.y + size.y}};
return result;
}
R2P_GetSize :: proc(r: R2P): V2 {
result := :V2{r.max.x - r.min.x, r.max.y - r.min.y};
return result;
}
V2_Mul :: proc(a: V2, b: V2): V2 {
result := :V2{a.x * b.x, a.y * b.y};
return result;
}
V2_MulF :: proc(a: V2, b: f32): V2 {
result := :V2{a.x * b, a.y * b};
return result;
}
V2_FromV2I :: proc(a: V2I): V2 {
result := :V2{:f32(a.x), :f32(a.y)};
return result;
}
I32_Max :: proc(a: i32, b: i32): i32 {
if a > b return a;
return b;
}
I32_Min :: proc(a: i32, b: i32): i32 {
if a > b return b;
return a;
}
F32_Max :: proc(a: f32, b: f32): f32 {
if a > b return a;
return b;
}
F32_Min :: proc(a: f32, b: f32): f32 {
if a > b return b;
return a;
}
F32_Clamp :: proc(val: f32, min: f32, max: f32): f32 {
if (val > max) return max;
if (val < min) return min;
return val;
}
R2P_CutLeft :: proc(r: *R2P, value: float): R2P {
minx := r.min.x;
r.min.x = F32_Min(r.max.x, r.min.x + value);
return :R2P{
{ minx, r.min.y},
{r.min.x, r.max.y}
};
}
R2P_CutRight :: proc(r: *R2P, value: f32): R2P {
maxx := r.max.x;
r.max.x = F32_Max(r.max.x - value, r.min.x);
return :R2P{
{r.max.x, r.min.y},
{ maxx, r.max.y}
};
}
R2P_CutTop :: proc(r: *R2P, value: f32): R2P { // Y is up
maxy := r.max.y;
r.max.y = F32_Max(r.min.y, r.max.y - value);
return :R2P{
{r.min.x, r.max.y},
{r.max.x, maxy}
};
}
R2P_CutBottom :: proc(r: *R2P, value: f32): R2P { // Y is up
miny := r.min.y;
r.min.y = F32_Min(r.min.y + value, r.max.y);
return :R2P{
{r.min.x, miny},
{r.max.x, r.min.y}
};
}
R2P_Shrink :: proc(r: R2P, size: f32): R2P {
return :R2P{
:V2{r.min.x + size, r.min.y + size},
:V2{r.max.x - size, r.max.y - size}
};
}
R2P_CollidesV2 :: proc(rect: R2P , point: V2): bool {
result := point.x > rect.min.x && point.x < rect.max.x && point.y > rect.min.y && point.y < rect.max.y;
return result;
}
R2P_CollidesR2P :: proc(a: R2P, b: R2P): bool {
result := a.min.x < b.max.x && a.max.x > b.min.x && a.min.y < b.max.y && a.max.y > b.min.y;
return result;
}
V2_Add :: proc(a: V2, b: V2): V2 { return {a.x + b.x, a.y + b.y}; }
V2_Sub :: proc(a: V2, b: V2): V2 { return {a.x - b.x, a.y - b.y}; }
V2_DivF :: proc(a: V2, b: f32): V2 { return {a.x / b, a.y / b}; }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,545 @@
R_CommandBufferSize :: 1024 * 1024;
R_Atlas :: struct {
bitmap : *u32;
sizei : V2I;
size : V2;
inverse_size : V2;
cursor : V2I;
biggest_height : i32;
white_texture_bounding_box : R2P;
texture_id : u32;
padding : V2I;
}
R_FontGlyph :: struct {
size : V2;
offset : V2;
x_advance : f32;
left_side_bearing : f32;
atlas_bounding_box : R2P;
}
R_Font :: struct {
atlas : *R_Atlas;
glyphs : [96]R_FontGlyph;
glyph_count : i32;
first_char : i32;
last_char : i32;
// This is for oversampling
// 0.5 = we draw the font as 2 times smaller then it is.
// Should lead to better quality result.
scaling_transform : f32;
// scaling transform is applied to these
size : f32;
ascent : f32;
descent : f32;
line_gap : f32;
// scaling factor not applied, not sure if these will be useful
scale : f32;
em_scale : f32;
white_texture_bounding_box : R2P;
}
R_Vertex2D :: struct {
pos : V2;
tex : V2;
color: V4;
}
R_CommandKind :: typedef int;
R_CommandKind_Null :: 0;
R_CommandKind_Triangle :: ^;
R_CommandKind_Triangle2D :: ^;
R_Command :: struct {
kind: R_CommandKind;
next: *R_Command;
out : *R_Vertex2D; // user write
data: *R_Vertex2D;
count: i32;
max_count: i32;
}
R_Shader :: struct {
pipeline: u32;
fragment: u32;
vertex : u32;
}
R_Render :: struct {
first_command2d: *R_Command;
last_command2d : *R_Command;
total_vertex_count: u64;
vbo: u32;
vao: u32;
shader2d: R_Shader;
atlas: R_Atlas;
font_medium: R_Font;
font: *R_Font;
}
R_Text2DDesc :: struct {
pos: V2;
text: S8_String;
color: V4;
scale: f32;
do_draw: bool;
rects_arena: *MA_Arena;
}
R_StringMeasure :: struct {
first_rect: *R_RectNode;
last_rect : *R_RectNode;
rect: R2P;
}
R_RectNode :: struct {
next: *R_RectNode;
rect: R2P;
utf8_codepoint_byte_size: i32;
}
R_PackType :: typedef int;
R_PackType_MonoColorFont :: 0;
R_PackType_RGBABitmap :: ^;
R_CreateAtlas :: proc(arena: *MA_Arena, size: V2I, padding: V2I): R_Atlas {
result: R_Atlas;
result.padding = padding;
result.sizei = size;
result.size = V2_FromV2I(size);
result.inverse_size.x = 1.0 / result.size.x;
result.inverse_size.y = 1.0 / result.size.y;
result.bitmap = MA_PushSize(arena, :usize(:i32(sizeof(:u32)) * size.x * size.y));
// Add a whitebox first for rectangle rendering
for y: i32 = 0; y < 16; y += 1 {
for x: i32 = 0; x < 16; x += 1 {
dst := &result.bitmap[x + y * result.sizei.x];
*dst = 0xffffffff;
}
}
// Skipping some pixels to avoid linear interpolation on edges
result.white_texture_bounding_box = :R2P{
{2.0 * result.inverse_size.x, 2.0 / result.size.y},
{14.0 * result.inverse_size.x, 14.0 / result.size.y},
};
result.cursor.x += 16 + padding.x;
result.biggest_height += 16 + padding.y;
return result;
}
R_PackBitmapInvertY :: proc(atlas: *R_Atlas, pack_type: R_PackType, bitmap: *u8, width: i32, height: i32): R2P {
// Packing into a texture atlas
// @Inefficient The algorithm is a simplest thing I had in mind, first we advance
// through the atlas in X packing consecutive glyphs. After we get to the end of the row
// we advance to the next row by the Y size of the biggest packed glyph. If we get to the
// end of atlas and fail to pack everything the app panics.
if (atlas.cursor.x + width > atlas.sizei.x) {
if (atlas.cursor.y + height < atlas.sizei.y) {
atlas.cursor.x = 0;
atlas.cursor.y += atlas.biggest_height + atlas.padding.y;
}
else {
IO_FatalErrorf("Error while packing a font into atlas. Atlas size for this font scale is a bit too small");
}
}
// Write the bitmap with inverted Y
src := bitmap;
// @todo: ambigious syntax, expression parsing doesn't stop at '{', error in wrong place
// for y := atlas.cursor.y + height - 1; y >= atlas.cursor.y; y++ {
for y := atlas.cursor.y + height - 1; y >= atlas.cursor.y; y -= 1 {
for x := atlas.cursor.x; x < atlas.cursor.x + width; x += 1 {
if (pack_type == R_PackType_RGBABitmap) {
atlas.bitmap[x + y * atlas.sizei.x] = *:*u32(src);
src = &src[4];
continue;
}
if (pack_type == R_PackType_MonoColorFont) {
dst := :*u8(&atlas.bitmap[x + y * atlas.sizei.x]);
dst[0] = 0xFF;
dst[1] = 0xFF;
dst[2] = 0xFF;
dst[3] = *src;
src = &src[1];
continue;
}
IO_InvalidCodepath();
}
}
size := :V2{:f32(width) * atlas.inverse_size.x, :f32(height) * atlas.inverse_size.y};
cursor := V2_FromV2I(atlas.cursor);
pos := V2_Mul(cursor, atlas.inverse_size);
result := R2P_Size(pos, size);
atlas.cursor.x += width + atlas.padding.x;
atlas.biggest_height = I32_Max(atlas.biggest_height, height);
return result;
}
Font_Create :: proc(atlas: *R_Atlas, size: f32, path: S8_String, oversampling: f32): R_Font {
scratch := MA_GetScratch();
font_file := OS_ReadFile(scratch.arena, path);
result: R_Font;
result.scaling_transform = 1.0 / oversampling;
result.size = oversampling * size;
result.first_char = ' ';
result.last_char = '~';
stb_font: stbtt_fontinfo;
if (font_file.len) {
success := stbtt_InitFont(&stb_font, :*uchar(font_file.str), 0);
if (success) {
ascent: int;
descent: int;
gap: int;
stbtt_GetFontVMetrics(&stb_font, &ascent, &descent, &gap);
result.scale = stbtt_ScaleForPixelHeight(&stb_font, result.size);
result.em_scale = stbtt_ScaleForMappingEmToPixels(&stb_font, result.size);
result.ascent = :f32(ascent) * result.scale;
result.descent = :f32(descent) * result.scale;
result.line_gap = :f32(gap) * result.scale;
result.white_texture_bounding_box = atlas.white_texture_bounding_box;
for ascii_symbol := result.first_char; ascii_symbol <= result.last_char; ascii_symbol+=1 {
width: int;
height: int;
xoff: int;
yoff: int;
bitmap := :*u8(stbtt_GetCodepointBitmap(&stb_font, 0, result.scale, :int(ascii_symbol), &width, &height, &xoff, &yoff));
x_advance: int;
left_side_bearing: int;
stbtt_GetCodepointHMetrics(&stb_font, :int(ascii_symbol), &x_advance, &left_side_bearing);
g := &result.glyphs[result.glyph_count];
result.glyph_count += 1;
g.atlas_bounding_box = R_PackBitmapInvertY(atlas, R_PackType_MonoColorFont, bitmap, :i32(width), :i32(height));
g.size = :V2{:f32(width), :f32(height)};
// Offset y needs to be inverted cause bitmap has inverted Y
g.offset = :V2{:f32(xoff), -(g.size.y + :f32(yoff))};
g.x_advance = :f32(x_advance) * result.scale;
g.left_side_bearing = :f32(left_side_bearing) * result.scale;
// Apply scaling transform
g.offset = V2_MulF(g.offset, result.scaling_transform);
g.x_advance = g.x_advance * result.scaling_transform;
g.left_side_bearing = g.left_side_bearing * result.scaling_transform;
g.size = V2_MulF(g.size, result.scaling_transform);
stbtt_FreeBitmap(:*uchar(bitmap), nil);
}
result.ascent *= result.scaling_transform;
result.descent *= result.scaling_transform;
result.size *= result.scaling_transform;
result.line_gap *= result.scaling_transform;
}
}
MA_EndTemp(scratch);
return result;
}
Font_GetGlyph :: proc(font: *R_Font, codepoint: i32): *R_FontGlyph {
is_in_range := codepoint >= font.first_char && codepoint <= font.last_char;
if (is_in_range) {
index := codepoint - font.first_char;
return &font.glyphs[index];
}
else {
index := '?' - font.first_char;
return &font.glyphs[index];
}
}
GL_DebugCallback :: proc(source: GLenum @unused, type: GLenum @unused, id: GLuint @unused, severity: GLenum, length: GLsizei @unused, message: *GLchar, user: *void @unused) {
IO_Printf("%s\n", message);
if (severity == GL_DEBUG_SEVERITY_HIGH || severity == GL_DEBUG_SEVERITY_MEDIUM) {
IO_FatalErrorf("%s", message);
}
}
GL_SetProcAddress :: proc(p: *void, name: *char) {
pp := :**void(p);
*pp = Mu.gl_get_proc_address(name);
}
GL_LoadProcs :: proc() {
load_up_to(4, 5, :*void(GL_SetProcAddress));
glDebugMessageCallback(:*void(GL_DebugCallback), nil);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
}
R_Reload :: proc() {
GL_LoadProcs();
}
R_Init :: proc(render: *R_Render) {
R = render;
GL_LoadProcs();
atlas := R_CreateAtlas(Temp, :V2I{1024, 1024}, :V2I{4, 4});
R.font_medium = Font_Create(&atlas, 32, S8_Lit("C:/windows/fonts/Calibri.ttf"), 1.0);
{
glCreateTextures(GL_TEXTURE_2D, 1, &atlas.texture_id);
glTextureParameteri(atlas.texture_id, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTextureParameteri(atlas.texture_id, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTextureParameteri(atlas.texture_id, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTextureParameteri(atlas.texture_id, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTextureStorage2D(atlas.texture_id, 1, GL_RGBA8, :i32(atlas.sizei.x), :i32(atlas.sizei.y));
glTextureSubImage2D(atlas.texture_id, 0, 0, 0, :i32(atlas.sizei.x), :i32(atlas.sizei.y), GL_RGBA, GL_UNSIGNED_BYTE, atlas.bitmap);
}
R.atlas = atlas;
R.font_medium.atlas = &R.atlas;
R.font = &R.font_medium;
glCreateBuffers(1, &R.vbo);
glNamedBufferStorage(R.vbo, R_CommandBufferSize, nil, GL_DYNAMIC_STORAGE_BIT);
{
glCreateVertexArrays(1, &R.vao);
vbuf_index: u32 = 0;
glVertexArrayVertexBuffer(R.vao, vbuf_index, R.vbo, 0, :i32(sizeof(:R_Vertex2D)));
a_pos: u32 = 0;
pos_offset: u32 = #`offsetof(R_Vertex2D, pos)`;
glVertexArrayAttribFormat(R.vao, a_pos, 2, GL_FLOAT, GL_FALSE, pos_offset);
glVertexArrayAttribBinding(R.vao, a_pos, vbuf_index);
glEnableVertexArrayAttrib(R.vao, a_pos);
a_tex: u32 = 1;
tex_offset: u32 = #`offsetof(R_Vertex2D, tex)`;
glVertexArrayAttribFormat(R.vao, a_tex, 2, GL_FLOAT, GL_FALSE, tex_offset);
glVertexArrayAttribBinding(R.vao, a_tex, vbuf_index);
glEnableVertexArrayAttrib(R.vao, a_tex);
a_color: u32 = 2;
color_offset: u32 = #`offsetof(R_Vertex2D, color)`; // @todo
glVertexArrayAttribFormat(R.vao, a_color, 4, GL_FLOAT, GL_FALSE, color_offset);
glVertexArrayAttribBinding(R.vao, a_color, vbuf_index);
glEnableVertexArrayAttrib(R.vao, a_color);
}
vshader := `#version 450 core
layout(location=0) uniform vec2 U_InvHalfScreenSize;
layout(location=0) in vec2 IN_Pos;
layout(location=1) in vec2 IN_Tex;
layout(location=2) in vec4 IN_Color;
out gl_PerVertex { vec4 gl_Position; }; // required because of ARB_separate_shader_objects
out vec2 OUT_UV;
out vec4 OUT_Color;
void main() {
vec2 pos = IN_Pos * U_InvHalfScreenSize;
pos -= vec2(1, 1);
gl_Position = vec4(pos, 0, 1);
OUT_UV = IN_Tex;
OUT_Color = IN_Color;
}
`;
fshader := `#version 450 core
in vec2 IN_UV;
in vec4 IN_Color;
layout (binding=0) uniform sampler2D S_Texture;
layout (location=0) out vec4 OUT_Color;
void main() {
vec4 c = IN_Color;
vec4 texture_color = texture(S_Texture, IN_UV);
OUT_Color = c * texture_color;
}
`;
R.shader2d = R_CreateShader(vshader, fshader);
}
R_CreateShader :: proc(glsl_vshader: *char, glsl_fshader: *char): R_Shader {
result: R_Shader;
result.vertex = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &glsl_vshader);
result.fragment = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &glsl_fshader);
linked: i32;
glGetProgramiv(result.vertex, GL_LINK_STATUS, &linked);
if (!linked) {
message: [1024]char;
glGetProgramInfoLog(result.vertex, :i32(sizeof(message)), nil, :*u8(&message[0]));
IO_FatalErrorf("[GL] Failed to create vertex shader! %s", message);
}
glGetProgramiv(result.fragment, GL_LINK_STATUS, &linked);
if (!linked) {
message: [1024]char;
glGetProgramInfoLog(result.fragment, :i32(sizeof(message)), nil, :*u8(&message[0]));
IO_FatalErrorf("[GL] Failed to create fragment shader! %s", message);
}
glGenProgramPipelines(1, &result.pipeline);
glUseProgramStages(result.pipeline, GL_VERTEX_SHADER_BIT, result.vertex);
glUseProgramStages(result.pipeline, GL_FRAGMENT_SHADER_BIT, result.fragment);
return result;
}
R_GetCommand :: proc(kind: R_CommandKind, vcount_to_add: i32): *R_Command {
make_new := R.last_command2d == 0 ||
R.last_command2d.kind != kind ||
R.last_command2d.count + vcount_to_add > R.last_command2d.max_count;
if make_new {
sizeof_r_command: usize = #`sizeof(R_Command)`;
c: *R_Command = MA_PushSize(Temp, sizeof_r_command);
c.kind = kind;
c.data = MA_PushSizeNonZeroed(Temp, R_CommandBufferSize);
if (kind == R_CommandKind_Triangle2D) {
c.max_count = R_CommandBufferSize / :i32(sizeof(:R_Vertex2D));
SLL_QUEUE_ADD(R.first_command2d, R.last_command2d, c);
}
else IO_InvalidCodepath();
}
c := R.last_command2d;
c.out = &c.data[c.count];
c.count += vcount_to_add;
R.total_vertex_count += :u64(vcount_to_add);
return c;
}
R_Rect2D :: proc(rect: R2P, tex: R2P, color: V4) {
c := R_GetCommand(R_CommandKind_Triangle2D, 6);
c.out[0].pos = :V2{rect.min.x, rect.max.y};
c.out[0].tex = :V2{tex.min.x, tex.max.y};
c.out[0].color = color;
c.out[1].pos = :V2{rect.max.x, rect.max.y};
c.out[1].tex = :V2{tex.max.x, tex.max.y};
c.out[1].color = color;
c.out[2].pos = :V2{rect.min.x, rect.min.y};
c.out[2].tex = :V2{tex.min.x, tex.min.y};
c.out[2].color = color;
c.out[3].pos = :V2{rect.min.x, rect.min.y};
c.out[3].tex = :V2{tex.min.x, tex.min.y};
c.out[3].color = color;
c.out[4].pos = :V2{rect.max.x, rect.max.y};
c.out[4].tex = :V2{tex.max.x, tex.max.y};
c.out[4].color = color;
c.out[5].pos = :V2{rect.max.x, rect.min.y};
c.out[5].tex = :V2{tex.max.x, tex.min.y};
c.out[5].color = color;
}
R_Text2D :: proc(params: R_Text2DDesc): R_StringMeasure {
result: R_StringMeasure;
original_pos := params.pos;
max_pos := params.pos;
pos := params.pos;
scale := params.scale;
for iter := UTF8_IterateEx(params.text.str, :int(params.text.len)); iter.item /*;UTF8_Advance(&iter)@todo*/ {
it: u32 = iter.item;
if it == '\n' {
pos.x = original_pos.x;
pos.y -= R.font.size * scale;
if (pos.x > max_pos.x) max_pos.x = pos.x;
if (pos.y < max_pos.y) max_pos.y = pos.y; // @warning: min position y actually
continue;
}
g: *R_FontGlyph = Font_GetGlyph(R.font, :i32(it));
sym_pos := pos;
pos.x += g.x_advance * scale;
if (pos.x > max_pos.x) max_pos.x = pos.x;
if (pos.y < max_pos.y) max_pos.y = pos.y; // @warning: min position y actually
sym_pos.x += g.offset.x * scale;
sym_pos.y += g.offset.y * scale;
minp: V2 = {sym_pos.x, sym_pos.y};
rect: R2P = R2P_Size(minp, V2_MulF(g.size, scale));
if (params.do_draw) {
R_Rect2D(rect, g.atlas_bounding_box, params.color);
}
if (params.rects_arena) {
node: *R_RectNode = MA_PushSize(params.rects_arena, :usize(sizeof(:R_RectNode)));
node.rect = rect;
node.utf8_codepoint_byte_size = :i32(iter.utf8_codepoint_byte_size);
SLL_QUEUE_ADD(result.first_rect, result.last_rect, node);
}
UTF8_Advance(&iter); // @todo
}
result.rect = {
{original_pos.x, max_pos.y + R.font.descent * scale}, /* @warning: min position y actually */
{max_pos.x, original_pos.y + R.font.ascent * scale},
};
return result;
}
R_GetTextSize :: proc(text: S8_String): f32 {
m := R_Text2D({text = text, scale = 1.0});
size := R2P_GetSize(m.rect).x;
return size;
}
R_ColorWhite: V4 = {1,1,1,1};
R_ColorBlack: V4 = {0,0,0,1};
R_DrawText :: proc(text: S8_String, pos: V2, color: V4): R_StringMeasure {
result := R_Text2D({text = text, color = color, pos = pos, do_draw = true, scale = 1.0});
return result;
}
R_EndFrame :: proc() {
ws := :V2{:f32(Mu.window.size.x), :f32(Mu.window.size.y)};
wsi := :V2I{:i32(Mu.window.size.x), :i32(Mu.window.size.y)};
x: f32 = 1 / (ws.x / 2);
y: f32 = 1 / (ws.y / 2);
glViewport(0, 0, wsi.x, wsi.y);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
// Default draw using the font texture
{
glBindProgramPipeline(R.shader2d.pipeline);
glProgramUniform2f(R.shader2d.vertex, 0, x, y);
for it := R.first_command2d; it; it = it.next {
if (it.kind == R_CommandKind_Triangle2D) {
glNamedBufferSubData(R.vbo, 0, :int(it.count) * :int(sizeof(:R_Vertex2D)), it.data);
glBindVertexArray(R.vao);
s_texture: u32 = 0; // texture unit that sampler2D will use in GLSL code
glBindTextureUnit(s_texture, R.font.atlas.texture_id);
glDrawArrays(GL_TRIANGLES, 0, it.count);
continue;
}
IO_InvalidCodepath();
}
}
R.first_command2d = nil;
R.last_command2d = nil;
R.total_vertex_count = 0;
}

View File

@@ -0,0 +1,183 @@
#`#include "vendor/stb_truetype.c"`;
@foreign stbrp_rect :: struct {f: int;}
@foreign
stbtt__buf :: struct {
data: *uchar;
cursor: int;
size: int;
}
@foreign
stbtt_bakedchar :: struct {
x0: ushort;
y0: ushort;
x1: ushort;
y1: ushort;
xoff: float;
yoff: float;
xadvance: float;
}
@foreign stbtt_BakeFontBitmap :: proc(data: *uchar, offset: int, pixel_height: float, pixels: *uchar, pw: int, ph: int, first_char: int, num_chars: int, chardata: *stbtt_bakedchar): int;
@foreign
stbtt_aligned_quad :: struct {
x0: float;
y0: float;
s0: float;
t0: float;
x1: float;
y1: float;
s1: float;
t1: float;
}
@foreign stbtt_GetBakedQuad :: proc(chardata: *stbtt_bakedchar, pw: int, ph: int, char_index: int, xpos: *float, ypos: *float, q: *stbtt_aligned_quad, opengl_fillrule: int);
@foreign stbtt_GetScaledFontVMetrics :: proc(fontdata: *uchar, index: int, size: float, ascent: *float, descent: *float, lineGap: *float);
@foreign
stbtt_packedchar :: struct {
x0: ushort;
y0: ushort;
x1: ushort;
y1: ushort;
xoff: float;
yoff: float;
xadvance: float;
xoff2: float;
yoff2: float;
}
@foreign stbtt_PackBegin :: proc(spc: *stbtt_pack_context, pixels: *uchar, width: int, height: int, stride_in_bytes: int, padding: int, alloc_context: *void): int;
@foreign stbtt_PackEnd :: proc(spc: *stbtt_pack_context);
@foreign stbtt_PackFontRange :: proc(spc: *stbtt_pack_context, fontdata: *uchar, font_index: int, font_size: float, first_unicode_char_in_range: int, num_chars_in_range: int, chardata_for_range: *stbtt_packedchar): int;
@foreign
stbtt_pack_range :: struct {
font_size: float;
first_unicode_codepoint_in_range: int;
array_of_unicode_codepoints: *int;
num_chars: int;
chardata_for_range: *stbtt_packedchar;
h_oversample: uchar;
v_oversample: uchar;
}
@foreign stbtt_PackFontRanges :: proc(spc: *stbtt_pack_context, fontdata: *uchar, font_index: int, ranges: *stbtt_pack_range, num_ranges: int): int;
@foreign stbtt_PackSetOversampling :: proc(spc: *stbtt_pack_context, h_oversample: uint, v_oversample: uint);
@foreign stbtt_PackSetSkipMissingCodepoints :: proc(spc: *stbtt_pack_context, skip: int);
@foreign stbtt_GetPackedQuad :: proc(chardata: *stbtt_packedchar, pw: int, ph: int, char_index: int, xpos: *float, ypos: *float, q: *stbtt_aligned_quad, align_to_integer: int);
@foreign stbtt_PackFontRangesGatherRects :: proc(spc: *stbtt_pack_context, info: *stbtt_fontinfo, ranges: *stbtt_pack_range, num_ranges: int, rects: *stbrp_rect): int;
@foreign stbtt_PackFontRangesPackRects :: proc(spc: *stbtt_pack_context, rects: *stbrp_rect, num_rects: int);
@foreign stbtt_PackFontRangesRenderIntoRects :: proc(spc: *stbtt_pack_context, info: *stbtt_fontinfo, ranges: *stbtt_pack_range, num_ranges: int, rects: *stbrp_rect): int;
@foreign
stbtt_pack_context :: struct {
user_allocator_context: *void;
pack_info: *void;
width: int;
height: int;
stride_in_bytes: int;
padding: int;
skip_missing: int;
h_oversample: uint;
v_oversample: uint;
pixels: *uchar;
nodes: *void;
}
@foreign stbtt_GetNumberOfFonts :: proc(data: *uchar): int;
@foreign stbtt_GetFontOffsetForIndex :: proc(data: *uchar, index: int): int;
@foreign
stbtt_fontinfo :: struct {
userdata: *void;
data: *uchar;
fontstart: int;
numGlyphs: int;
loca: int;
head: int;
glyf: int;
hhea: int;
hmtx: int;
kern: int;
gpos: int;
svg: int;
index_map: int;
indexToLocFormat: int;
cff: stbtt__buf;
charstrings: stbtt__buf;
gsubrs: stbtt__buf;
subrs: stbtt__buf;
fontdicts: stbtt__buf;
fdselect: stbtt__buf;
}
@foreign stbtt_InitFont :: proc(info: *stbtt_fontinfo, data: *uchar, offset: int): int;
@foreign stbtt_FindGlyphIndex :: proc(info: *stbtt_fontinfo, unicode_codepoint: int): int;
@foreign stbtt_ScaleForPixelHeight :: proc(info: *stbtt_fontinfo, pixels: float): float;
@foreign stbtt_ScaleForMappingEmToPixels :: proc(info: *stbtt_fontinfo, pixels: float): float;
@foreign stbtt_GetFontVMetrics :: proc(info: *stbtt_fontinfo, ascent: *int, descent: *int, lineGap: *int);
@foreign stbtt_GetFontVMetricsOS2 :: proc(info: *stbtt_fontinfo, typoAscent: *int, typoDescent: *int, typoLineGap: *int): int;
@foreign stbtt_GetFontBoundingBox :: proc(info: *stbtt_fontinfo, x0: *int, y0: *int, x1: *int, y1: *int);
@foreign stbtt_GetCodepointHMetrics :: proc(info: *stbtt_fontinfo, codepoint: int, advanceWidth: *int, leftSideBearing: *int);
@foreign stbtt_GetCodepointKernAdvance :: proc(info: *stbtt_fontinfo, ch1: int, ch2: int): int;
@foreign stbtt_GetCodepointBox :: proc(info: *stbtt_fontinfo, codepoint: int, x0: *int, y0: *int, x1: *int, y1: *int): int;
@foreign stbtt_GetGlyphHMetrics :: proc(info: *stbtt_fontinfo, glyph_index: int, advanceWidth: *int, leftSideBearing: *int);
@foreign stbtt_GetGlyphKernAdvance :: proc(info: *stbtt_fontinfo, glyph1: int, glyph2: int): int;
@foreign stbtt_GetGlyphBox :: proc(info: *stbtt_fontinfo, glyph_index: int, x0: *int, y0: *int, x1: *int, y1: *int): int;
@foreign
stbtt_kerningentry :: struct {
glyph1: int;
glyph2: int;
advance: int;
}
@foreign stbtt_GetKerningTableLength :: proc(info: *stbtt_fontinfo): int;
@foreign stbtt_GetKerningTable :: proc(info: *stbtt_fontinfo, table: *stbtt_kerningentry, table_length: int): int;
@foreign
stbtt_vertex :: struct {
x: short;
y: short;
cx: short;
cy: short;
cx1: short;
cy1: short;
type: uchar;
padding: uchar;
}
@foreign stbtt_IsGlyphEmpty :: proc(info: *stbtt_fontinfo, glyph_index: int): int;
@foreign stbtt_GetCodepointShape :: proc(info: *stbtt_fontinfo, unicode_codepoint: int, vertices: **stbtt_vertex): int;
@foreign stbtt_GetGlyphShape :: proc(info: *stbtt_fontinfo, glyph_index: int, vertices: **stbtt_vertex): int;
@foreign stbtt_FreeShape :: proc(info: *stbtt_fontinfo, vertices: *stbtt_vertex);
@foreign stbtt_FindSVGDoc :: proc(info: *stbtt_fontinfo, gl: int): *uchar;
@foreign stbtt_GetCodepointSVG :: proc(info: *stbtt_fontinfo, unicode_codepoint: int, svg: **char): int;
@foreign stbtt_GetGlyphSVG :: proc(info: *stbtt_fontinfo, gl: int, svg: **char): int;
@foreign stbtt_FreeBitmap :: proc(bitmap: *uchar, userdata: *void);
@foreign stbtt_GetCodepointBitmap :: proc(info: *stbtt_fontinfo, scale_x: float, scale_y: float, codepoint: int, width: *int, height: *int, xoff: *int, yoff: *int): *uchar;
@foreign stbtt_GetCodepointBitmapSubpixel :: proc(info: *stbtt_fontinfo, scale_x: float, scale_y: float, shift_x: float, shift_y: float, codepoint: int, width: *int, height: *int, xoff: *int, yoff: *int): *uchar;
@foreign stbtt_MakeCodepointBitmap :: proc(info: *stbtt_fontinfo, output: *uchar, out_w: int, out_h: int, out_stride: int, scale_x: float, scale_y: float, codepoint: int);
@foreign stbtt_MakeCodepointBitmapSubpixel :: proc(info: *stbtt_fontinfo, output: *uchar, out_w: int, out_h: int, out_stride: int, scale_x: float, scale_y: float, shift_x: float, shift_y: float, codepoint: int);
@foreign stbtt_MakeCodepointBitmapSubpixelPrefilter :: proc(info: *stbtt_fontinfo, output: *uchar, out_w: int, out_h: int, out_stride: int, scale_x: float, scale_y: float, shift_x: float, shift_y: float, oversample_x: int, oversample_y: int, sub_x: *float, sub_y: *float, codepoint: int);
@foreign stbtt_GetCodepointBitmapBox :: proc(font: *stbtt_fontinfo, codepoint: int, scale_x: float, scale_y: float, ix0: *int, iy0: *int, ix1: *int, iy1: *int);
@foreign stbtt_GetCodepointBitmapBoxSubpixel :: proc(font: *stbtt_fontinfo, codepoint: int, scale_x: float, scale_y: float, shift_x: float, shift_y: float, ix0: *int, iy0: *int, ix1: *int, iy1: *int);
@foreign stbtt_GetGlyphBitmap :: proc(info: *stbtt_fontinfo, scale_x: float, scale_y: float, glyph: int, width: *int, height: *int, xoff: *int, yoff: *int): *uchar;
@foreign stbtt_GetGlyphBitmapSubpixel :: proc(info: *stbtt_fontinfo, scale_x: float, scale_y: float, shift_x: float, shift_y: float, glyph: int, width: *int, height: *int, xoff: *int, yoff: *int): *uchar;
@foreign stbtt_MakeGlyphBitmap :: proc(info: *stbtt_fontinfo, output: *uchar, out_w: int, out_h: int, out_stride: int, scale_x: float, scale_y: float, glyph: int);
@foreign stbtt_MakeGlyphBitmapSubpixel :: proc(info: *stbtt_fontinfo, output: *uchar, out_w: int, out_h: int, out_stride: int, scale_x: float, scale_y: float, shift_x: float, shift_y: float, glyph: int);
@foreign stbtt_MakeGlyphBitmapSubpixelPrefilter :: proc(info: *stbtt_fontinfo, output: *uchar, out_w: int, out_h: int, out_stride: int, scale_x: float, scale_y: float, shift_x: float, shift_y: float, oversample_x: int, oversample_y: int, sub_x: *float, sub_y: *float, glyph: int);
@foreign stbtt_GetGlyphBitmapBox :: proc(font: *stbtt_fontinfo, glyph: int, scale_x: float, scale_y: float, ix0: *int, iy0: *int, ix1: *int, iy1: *int);
@foreign stbtt_GetGlyphBitmapBoxSubpixel :: proc(font: *stbtt_fontinfo, glyph: int, scale_x: float, scale_y: float, shift_x: float, shift_y: float, ix0: *int, iy0: *int, ix1: *int, iy1: *int);
@foreign
stbtt__bitmap :: struct {
w: int;
h: int;
stride: int;
pixels: *uchar;
}
@foreign stbtt_Rasterize :: proc(result: *stbtt__bitmap, flatness_in_pixels: float, vertices: *stbtt_vertex, num_verts: int, scale_x: float, scale_y: float, shift_x: float, shift_y: float, x_off: int, y_off: int, invert: int, userdata: *void);
@foreign stbtt_FreeSDF :: proc(bitmap: *uchar, userdata: *void);
@foreign stbtt_GetGlyphSDF :: proc(info: *stbtt_fontinfo, scale: float, glyph: int, padding: int, onedge_value: uchar, pixel_dist_scale: float, width: *int, height: *int, xoff: *int, yoff: *int): *uchar;
@foreign stbtt_GetCodepointSDF :: proc(info: *stbtt_fontinfo, scale: float, codepoint: int, padding: int, onedge_value: uchar, pixel_dist_scale: float, width: *int, height: *int, xoff: *int, yoff: *int): *uchar;
@foreign stbtt_FindMatchingFont :: proc(fontdata: *uchar, name: *char, flags: int): int;
@foreign stbtt_CompareUTF8toUTF16_bigendian :: proc(s1: *char, len1: int, s2: *char, len2: int): int;
@foreign stbtt_GetFontNameString :: proc(font: *stbtt_fontinfo, length: *int, platformID: int, encodingID: int, languageID: int, nameID: int): *char;

View File

@@ -0,0 +1,342 @@
Ui: UI_State;
UI_TextAlign :: typedef int;
UI_TextAlign_Center :: 0;
UI_TextAlign_CenterLeft :: ^;
UI_Layout :: struct {
rect: R2P;
}
UI_Widget :: struct {
next: *UI_Widget;
id: u64;
last_touched_frame_index: u64;
}
UI_Cut :: typedef int;
UI_Cut_Left :: 0;
UI_Cut_Right :: ^;
UI_Cut_Top :: ^;
UI_Cut_Bottom :: ^;
UI_Style :: struct {
cut_size: V2;
text_align: UI_TextAlign;
scroll: V2;
absolute: bool;
absolute_rect: R2P;
button_background_color: V4;
hot_button_background_color: V4;
interacting_with_button_background_color: V4;
active_button_background_color: V4;
text_color: V4;
button_border_color: V4;
checked_checkbox_button_background_color: V4;
draw_text_shadow: bool;
}
UI_BaseButtonDesc :: struct {
text: S8_String;
draw_text: bool;
draw_border: bool;
draw_background: bool;
use_checked_checkbox_button_background_colors: bool;
}
UI_Result :: struct {
pressed: bool;
interacting_with: bool;
hot: bool;
drag: V2;
}
UI_LayoutStack :: struct {
data: [256]UI_Layout;
len : i32;
}
UI_State :: struct {
cut: UI_Cut;
s: UI_Style; // current style
base_style: UI_Style;
// @todo: add stack id concept
cached_widgets: LC_Map; // @todo: add removing widgets at end of frame
layouts: UI_LayoutStack;
font: *R_Font;
hot: u64;
interacting_with: u64;
id: u64;
inside_begin_end_pair: bool;
// next cut output
text_width: f32;
text_size: V2;
}
UI_AddLayout :: proc(s: *UI_LayoutStack, layout: UI_Layout) {
if s.len + 1 > lengthof(s.data) {
IO_FatalErrorf("layout stack overflow reached max item count: %d", lengthof(s.data));
}
s.data[s.len] = layout;
s.len += 1;
}
UI_GetLastLayout :: proc(s: *UI_LayoutStack): *UI_Layout {
if (s.len == 0) IO_FatalErrorf("error, trying to get last layout but there are none");
return &s.data[s.len - 1];
}
UI_Begin :: proc() {
IO_Assert(Ui.inside_begin_end_pair == false);
Ui.inside_begin_end_pair = true;
IO_Assert(Ui.layouts.len == 0 || Ui.layouts.len == 1);
Ui.layouts.len = 0;
UI_AddLayout(&Ui.layouts, {R2P_SizeF(0, 0, :f32(Mu.window.size.x), :f32(Mu.window.size.y))});
UI_SetStyle();
}
UI_SetStyle :: proc() {
Ui.font = R.font;
Ui.base_style = {
cut_size = :V2{-1, Ui.font.size + 4},
button_background_color = :V4{0, 0, 0, 1.0},
text_color = :V4{1, 1, 1, 1},
button_border_color = :V4{1, 1, 1, 1},
checked_checkbox_button_background_color = :V4{0.5, 0.5, 0.5, 1.0},
hot_button_background_color = :V4{0.5, 0, 0, 1},
interacting_with_button_background_color = :V4{1.0, 0, 0, 1},
active_button_background_color = :V4{1, 0, 0, 1},
};
Ui.s = Ui.base_style;
}
UI_End :: proc() {
IO_Assert(Ui.inside_begin_end_pair);
Ui.inside_begin_end_pair = false;
if (Mu.window.mouse.left.unpress) {
Ui.interacting_with = NULL;
}
Ui.hot = NULL;
}
R_DrawBorder :: proc(r: R2P, color: V4) {
r = R2P_Shrink(r, 1);
R_Rect2D(R2P_CutLeft(&r, 1), R.atlas.white_texture_bounding_box, color);
R_Rect2D(R2P_CutRight(&r, 1), R.atlas.white_texture_bounding_box, color);
R_Rect2D(R2P_CutTop(&r, 1), R.atlas.white_texture_bounding_box, color);
R_Rect2D(R2P_CutBottom(&r, 1), R.atlas.white_texture_bounding_box, color);
}
UI_GetNextRect :: proc(text: S8_String): R2P {
if (Ui.s.absolute) return Ui.s.absolute_rect;
l := UI_GetLastLayout(&Ui.layouts);
cut := Ui.s.cut_size;
font := Ui.font;
if (text.len) {
Ui.text_width = R_GetTextSize(text);
Ui.text_size = :V2{Ui.text_width, font.ascent};
}
if (cut.x < 0) {
cut.x = Ui.text_width + 32;
if (cut.x < 0) {
cut.x = 32;
}
}
if (cut.y < 0) {
cut.y = font.ascent;
}
if (Ui.cut == UI_Cut_Top || Ui.cut == UI_Cut_Bottom) {
scroll_y := F32_Clamp(Ui.s.scroll.y, -cut.y, cut.y);
cut.y -= scroll_y;
Ui.s.scroll.y -= scroll_y;
}
if (Ui.cut == UI_Cut_Left || Ui.cut == UI_Cut_Right) {
scrollx := F32_Clamp(Ui.s.scroll.x, -cut.x, cut.x);
cut.x -= scrollx;
Ui.s.scroll.x -= scrollx;
}
if (Ui.cut == UI_Cut_Left) {
return R2P_CutLeft(&l.rect, cut.x);
}
if (Ui.cut == UI_Cut_Right) {
return R2P_CutRight(&l.rect, cut.x);
}
if (Ui.cut == UI_Cut_Top) {
return R2P_CutTop(&l.rect, cut.y);
}
if (Ui.cut == UI_Cut_Bottom) {
return R2P_CutBottom(&l.rect, cut.y);
}
IO_InvalidCodepath();
return :R2P{};
}
UI_GetWidget :: proc(text: S8_String): *UI_Widget {
if (Ui.cached_widgets.allocator.p == NULL) {
Ui.cached_widgets.allocator = Perm.allocator;
}
hash := HashBytes(text.str, :u64(text.len));
widget: *UI_Widget = LC_MapGetU64(&Ui.cached_widgets, hash);
if (!widget) {
widget = MA_PushSize(Perm, :usize(sizeof(:UI_Widget)));
widget.id = hash;
LC_MapInsertU64(&Ui.cached_widgets, hash, widget);
}
IO_Assert(widget.id == hash);
widget.last_touched_frame_index = :u64(Mu.frame);
return widget;
}
UI_BaseButton :: proc(desc: UI_BaseButtonDesc): UI_Result {
result: UI_Result;
rect := UI_GetNextRect(desc.text);
mouse_pos: V2 = {:f32(Mu.window.mouse.pos.x), :f32(Mu.window.mouse.pos.y)};
delta_mouse_pos: V2 = {:f32(Mu.window.mouse.delta_pos.x), :f32(Mu.window.mouse.delta_pos.y)};
if (rect.min.x == rect.max.x || rect.min.y == rect.max.y) {
return result;
}
widget := UI_GetWidget(desc.text);
if (R2P_CollidesV2(rect, mouse_pos)) {
Ui.hot = widget.id;
}
if (Ui.hot == widget.id) {
result.hot = true;
if (Mu.window.mouse.left.press) {
Ui.interacting_with = widget.id;
}
}
if (Ui.interacting_with == widget.id) {
result.interacting_with = true;
result.drag = delta_mouse_pos;
if (Ui.hot == widget.id && Mu.window.mouse.left.unpress) {
result.pressed = true;
}
}
text_pos := rect.min;
rect_size := R2P_GetSize(rect);
centered_text_pos := V2_Add(text_pos, V2_DivF(V2_Sub(rect_size, Ui.text_size), 2.0));
if (Ui.s.text_align == UI_TextAlign_Center) {
text_pos = centered_text_pos;
}
if (Ui.s.text_align == UI_TextAlign_CenterLeft) {
text_pos.y = centered_text_pos.y;
text_pos.x += 4;
}
button_background_color: V4 = Ui.s.button_background_color;
text_color: V4 = Ui.s.text_color;
button_border_color: V4 = Ui.s.button_border_color;
if (desc.use_checked_checkbox_button_background_colors) {
button_background_color = Ui.s.checked_checkbox_button_background_color;
}
if (Ui.hot == widget.id) {
button_background_color = Ui.s.hot_button_background_color;
}
if (Ui.interacting_with == widget.id) {
button_background_color = Ui.s.interacting_with_button_background_color;
}
if (result.pressed) {
button_background_color = Ui.s.active_button_background_color;
}
if (desc.draw_background) {
R_Rect2D(rect, R.atlas.white_texture_bounding_box, button_background_color);
}
if (desc.draw_border) {
R_DrawBorder(rect, button_border_color);
}
if (desc.draw_text) {
if (Ui.s.draw_text_shadow) {
R_Text2D({
text = desc.text,
color = R_ColorWhite,
pos = :V2{text_pos.x + 2, text_pos.y - 2},
do_draw = true,
scale = 1.0,
});
}
R_DrawText(desc.text, text_pos, text_color);
}
return result;
}
UI_Button :: proc(str: *char): bool {
result := UI_BaseButton({
text = S8_MakeFromChar(str),
draw_text = true,
draw_border = true,
draw_background = true,
});
return result.pressed;
}
UI_Checkbox :: proc(val: *bool, str: *char): bool {
result: UI_Result = UI_BaseButton({
text = S8_MakeFromChar(str),
draw_text = true,
draw_border = true,
draw_background = true,
use_checked_checkbox_button_background_colors = *val,
});
if (result.pressed) {
*val = !*val;
}
return *val;
}
UI_Fill :: proc() {
rect := UI_GetNextRect(S8_MakeEmpty());
R_Rect2D(rect, R.atlas.white_texture_bounding_box, Ui.s.button_background_color);
R_DrawBorder(rect, Ui.s.button_border_color);
}
UI_PopStyle :: proc() {
Ui.s = Ui.base_style;
}
UI_PopLayout :: proc() {
if Ui.layouts.len == 0 {
IO_FatalError("tryign to pop a layout but layout stack is empty");
}
Ui.layouts.len -= 1;
}
UI_PushLayout :: proc(rect_override: R2P): R2P {
rect: R2P;
if (rect_override.min.x != 0 || rect_override.min.y != 0 || rect_override.max.x != 0 || rect_override.max.y != 0) {
rect = rect_override;
}
else {
rect = UI_GetNextRect(S8_MakeEmpty());
}
UI_AddLayout(&Ui.layouts, {rect});
return rect;
}