This commit is contained in:
2024-01-19 23:55:07 +02:00
commit 156021347f
29 changed files with 4960 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
package zephr
// TODO: do audio which requries writing a binding for fmod
+57
View File
@@ -0,0 +1,57 @@
package zephr
import "core:fmt"
import gl "vendor:OpenGL"
Shader :: struct {
program: u32
}
create_shader :: proc(vertex_path: string, fragment_path: string) -> (Shader, bool) {
shader: Shader
program, success := gl.load_shaders(vertex_path, fragment_path)
shader.program = program;
return shader, success;
}
use_shader :: proc(shader: Shader) {
gl.UseProgram(shader.program);
}
set_mat4f :: proc(shader: Shader, name: cstring, mat4: Mat4, transpose: bool = false) {
mat4 := mat4
loc := gl.GetUniformLocation(shader.program, name);
gl.UniformMatrix4fv(loc, 1, transpose, raw_data(&mat4))
}
set_float :: proc(shader: Shader, name: cstring, val: f32) {
loc := gl.GetUniformLocation(shader.program, name);
gl.Uniform1f(loc, val);
}
set_int :: proc(shader: Shader, name: cstring, val: i32) {
loc := gl.GetUniformLocation(shader.program, name);
gl.Uniform1i(loc, val);
}
set_bool :: proc(shader: Shader, name: cstring, val: bool) {
set_int(shader, name, cast(i32)val);
}
set_vec2f :: proc(shader: Shader, name: cstring, val1: f32, val2: f32) {
loc := gl.GetUniformLocation(shader.program, name);
gl.Uniform2f(loc, val1, val2);
}
set_vec3f :: proc(shader: Shader, name: cstring, val1: f32, val2: f32, val3: f32) {
loc := gl.GetUniformLocation(shader.program, name);
gl.Uniform3f(loc, val1, val2, val3);
}
set_vec4f :: proc(shader: Shader, name: cstring, val1: f32, val2: f32, val3: f32, val4: f32) {
loc := gl.GetUniformLocation(shader.program, name);
gl.Uniform4f(loc, val1, val2, val3, val4);
}
+419
View File
@@ -0,0 +1,419 @@
package zephr
import "core:fmt"
import gl "vendor:OpenGL"
import FT "../freetype"
Character :: struct {
size: Vec2,
bearing: Vec2,
advance: u32,
tex_coords: [4]Vec2,
}
Font :: struct {
atlas_tex_id: TextureId,
characters: [128]Character,
}
// GlyphInstance needs to be 100 bytes (meaning no padding) otherwise the data we send to the shader
// is misaligned and we get rendering errors
#assert(size_of(GlyphInstance) == 100)
GlyphInstance :: struct #packed {
pos: Vec4,
tex_coords_idx: u32,
color: Vec4,
model: Mat4,
}
GlyphInstanceList :: [dynamic]GlyphInstance
@private
FONT_PIXEL_SIZE :: 100
@private
LINE_HEIGHT :: 2.0
@private
font_shader : Shader
@private
font_vao : u32
@private
font_instance_vbo : u32
init_freetype :: proc(font_path: cstring) -> i32 {
ft: FT.Library;
if (FT.Init_FreeType(&ft) != 0) {
return -1;
}
face: FT.Face;
if (FT.New_Face(ft, font_path, 0, &face) != 0) {
return -2;
}
// sets the variable font to be bold
/* if ((face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS)) { */
/* printf("[INFO] Got a variable font\n"); */
/* FT_MM_Var *mm; */
/* FT_Get_MM_Var(face, &mm); */
/* FT_Set_Var_Design_Coordinates(face, mm->num_namedstyles, mm->namedstyle[mm->num_namedstyles - 4].coords); */
/* FT_Done_MM_Var(ft, mm); */
/* } */
FT.Set_Pixel_Sizes(face, 0, FONT_PIXEL_SIZE);
gl.PixelStorei(gl.UNPACK_ALIGNMENT, 1);
pen_x, pen_y: u32
/* FT_UInt glyph_idx; */
/* FT_ULong c = FT_Get_First_Char(face, &glyph_idx); */
tex_width, tex_height: u32;
for i in 32..<128 {
if (FT.Load_Char(face, cast(u64)i, .RENDER) != 0) {
fmt.printf("[ERROR]: failed to load glyph for char '0x%x'\n", i);
}
/* FT_Render_Glyph(face->glyph, FT_RENDER_MODE_SDF); */
tex_width += face.glyph.bitmap.width + 1;
tex_height = max(tex_height, face.glyph.bitmap.rows);
}
pixels := make([dynamic]u8, tex_width * tex_height);
defer delete(pixels)
for i in 32..<128 {
/* while (glyph_idx) { */
if (FT.Load_Char(face, cast(u64)i, .RENDER) != 0) {
fmt.printf("[ERROR]: failed to load glyph for char '0x%x'\n", i);
}
/* FT_Render_Glyph(face->glyph, FT_RENDER_MODE_SDF); */
bmp := &face.glyph.bitmap;
if (pen_x + bmp.width >= tex_width) {
pen_x = 0;
pen_y += cast(u32)(1 + (face.size.metrics.height >> 6));
}
for row in 0..<bmp.rows {
for col in 0..<bmp.width {
x := pen_x + col;
y := pen_y + row;
pixels[y * tex_width + x] = bmp.buffer[row * cast(u32)bmp.pitch + col];
}
}
atlas_x0 := cast(f32)pen_x / cast(f32)tex_width;
atlas_y0 := cast(f32)pen_y / cast(f32)tex_height;
atlas_x1 := cast(f32)(pen_x + bmp.width) / cast(f32)tex_width;
atlas_y1 := cast(f32)(pen_y + bmp.rows) / cast(f32)tex_height;
top_left := Vec2{ atlas_x0, atlas_y1 };
top_right := Vec2{ atlas_x1, atlas_y1 };
bottom_right := Vec2{ atlas_x1, atlas_y0 };
bottom_left := Vec2{ atlas_x0, atlas_y0 };
character: Character
character.tex_coords[0] = top_left;
character.tex_coords[1] = top_right;
character.tex_coords[2] = bottom_right;
character.tex_coords[3] = bottom_left;
character.advance = cast(u32)face.glyph.advance.x;
character.size = Vec2{ cast(f32)face.glyph.bitmap.width, cast(f32)face.glyph.bitmap.rows };
character.bearing = Vec2{ cast(f32)face.glyph.bitmap_left, cast(f32)face.glyph.bitmap_top };
zephr_ctx.font.characters[i] = character;
pen_x += bmp.width + 1;
/* c = FT_Get_Next_Char(face, c, &glyph_idx); */
}
texture: TextureId;
gl.GenTextures(1, &texture);
gl.BindTexture(gl.TEXTURE_2D, texture);
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, cast(i32)tex_width,
cast(i32)tex_height, 0, gl.RED, gl.UNSIGNED_BYTE,
raw_data(pixels))
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
zephr_ctx.font.atlas_tex_id = texture;
gl.BindTexture(gl.TEXTURE_2D, 0);
FT.Done_Face(face);
FT.Done_FreeType(ft);
return 0;
}
init_fonts :: proc(font_path: cstring) -> i32 {
font_vbo, font_ebo: u32;
res := init_freetype(font_path);
if (res != 0) {
return res;
}
l_font_shader, success := create_shader("shaders/font.vert", "shaders/font.frag");
if (!success) {
fmt.println("[FATAL] Failed to create font shader")
return -2;
}
font_shader = l_font_shader;
gl.GenVertexArrays(1, &font_vao);
gl.GenBuffers(1, &font_vbo);
gl.GenBuffers(1, &font_instance_vbo);
gl.GenBuffers(1, &font_ebo);
quad_vertices := [4][2]f32 {
{0.0, 1.0}, // top left
{1.0, 1.0}, // top right
{1.0, 0.0}, // bottom right
{0.0, 0.0}, // bottom left
};
quad_indices := [6]u32 {
0, 1, 2,
2, 3, 0,
};
gl.BindVertexArray(font_vao);
// font quad vbo
gl.BindBuffer(gl.ARRAY_BUFFER, font_vbo);
gl.BufferData(gl.ARRAY_BUFFER, size_of(quad_vertices), &quad_vertices, gl.STATIC_DRAW);
gl.EnableVertexAttribArray(0);
gl.VertexAttribPointer(0, 2, gl.FLOAT, gl.FALSE, 2 * size_of(f32), 0);
// font instance vbo
gl.BindBuffer(gl.ARRAY_BUFFER, font_instance_vbo);
gl.EnableVertexAttribArray(1);
gl.VertexAttribPointer(1, 4, gl.FLOAT, gl.FALSE, size_of(GlyphInstance), 0);
gl.VertexAttribDivisor(1, 1);
gl.EnableVertexAttribArray(2);
gl.VertexAttribIPointer(2, 1, gl.INT, size_of(GlyphInstance), size_of(Vec4));
gl.VertexAttribDivisor(2, 1);
gl.EnableVertexAttribArray(3);
gl.VertexAttribPointer(3, 4, gl.FLOAT, gl.FALSE, size_of(GlyphInstance), (size_of(Vec4) + size_of(i32)));
gl.VertexAttribDivisor(3, 1);
gl.EnableVertexAttribArray(4);
gl.EnableVertexAttribArray(5);
gl.EnableVertexAttribArray(6);
gl.EnableVertexAttribArray(7);
gl.VertexAttribPointer(4, 4, gl.FLOAT, gl.FALSE, size_of(GlyphInstance), (size_of(Vec4) * 2 + size_of(i32)));
gl.VertexAttribPointer(5, 4, gl.FLOAT, gl.FALSE, size_of(GlyphInstance), (size_of(Vec4) * 3 + size_of(i32)));
gl.VertexAttribPointer(6, 4, gl.FLOAT, gl.FALSE, size_of(GlyphInstance), (size_of(Vec4) * 4 + size_of(i32)));
gl.VertexAttribPointer(7, 4, gl.FLOAT, gl.FALSE, size_of(GlyphInstance), (size_of(Vec4) * 5 + size_of(i32)));
gl.VertexAttribDivisor(4, 1);
gl.VertexAttribDivisor(5, 1);
gl.VertexAttribDivisor(6, 1);
gl.VertexAttribDivisor(7, 1);
// font ebo
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, font_ebo);
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, size_of(quad_indices), &quad_indices, gl.STATIC_DRAW);
use_shader(font_shader);
for i in 32..<128 {
for j in 0..<4 {
buf: [24]byte
text := fmt.bprintf(buf[:], "texcoords[%d]", (i - 32) * 4 + j);
set_vec2f(font_shader, cstring(raw_data(text)), zephr_ctx.font.characters[i].tex_coords[j].x, zephr_ctx.font.characters[i].tex_coords[j].y);
}
}
gl.BindBuffer(gl.ARRAY_BUFFER, 0);
gl.BindVertexArray(0);
return 0;
}
calculate_text_size :: proc(text: string, font_size: u32) -> Vec2 {
scale := cast(f32)font_size / FONT_PIXEL_SIZE;
size : Vec2 = ---
w: f32 = 0;
h: f32 = 0;
max_bearing_h: f32 = 0;
// NOTE: I don't like looping through the characters twice, but it's fine for now
for i in 0..<len(text) {
ch := zephr_ctx.font.characters[text[i]];
max_bearing_h = max(max_bearing_h, ch.bearing.y);
}
for i in 0..<len(text) {
ch := zephr_ctx.font.characters[text[i]];
w += cast(f32)ch.advance / 64;
// remove bearing of first character
if (i == 0 && len(text) > 1) {
w -= ch.bearing.x;
}
// remove the trailing width of the last character
if (i == len(text) - 1) {
w -= ((cast(f32)ch.advance / 64) - (ch.bearing.x + ch.size.x));
}
// if we only have one character in the text, then remove the bearing width
if (len(text) == 1) {
w -= (ch.bearing.x);
}
h = max(h, max_bearing_h - ch.bearing.y + ch.size.y);
if (text[i] == '\n') {
w = 0;
h += max_bearing_h;
}
}
size.x = w * scale;
size.y = h * scale;
return size;
}
draw_text :: proc(text: string, font_size: u32, constraints: UiConstraints, color: Color, alignment: Alignment) {
glyph_instance_list := get_glyph_instance_list_from_text(text, font_size, constraints, color, alignment);
gl.ActiveTexture(gl.TEXTURE0);
gl.BindTexture(gl.TEXTURE_2D, zephr_ctx.font.atlas_tex_id);
gl.BindVertexArray(font_vao);
gl.BindBuffer(gl.ARRAY_BUFFER, font_instance_vbo);
gl.BufferData(gl.ARRAY_BUFFER, size_of(GlyphInstance) * len(glyph_instance_list), raw_data(glyph_instance_list), gl.DYNAMIC_DRAW);
gl.DrawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_INT, nil, cast(i32)len(glyph_instance_list))
gl.BindVertexArray(0);
gl.BindTexture(gl.TEXTURE_2D, 0);
delete(glyph_instance_list)
}
draw_text_batch :: proc(batch: ^GlyphInstanceList) {
gl.ActiveTexture(gl.TEXTURE0);
gl.BindTexture(gl.TEXTURE_2D, zephr_ctx.font.atlas_tex_id);
gl.BindVertexArray(font_vao);
gl.BindBuffer(gl.ARRAY_BUFFER, font_instance_vbo);
gl.BufferData(gl.ARRAY_BUFFER, size_of(GlyphInstance) * len(batch), raw_data(batch^), gl.DYNAMIC_DRAW);
gl.DrawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_INT, nil, cast(i32)len(batch))
gl.BindVertexArray(0);
gl.BindTexture(gl.TEXTURE_2D, 0);
delete(batch^)
}
get_glyph_instance_list_from_text :: proc(text: string, font_size: u32, constraints: UiConstraints, color: Color, alignment: Alignment) -> GlyphInstanceList {
constraints := constraints
use_shader(font_shader);
text_color := Vec4{ cast(f32)color.r / 255, cast(f32)color.g / 255, cast(f32)color.b / 255, cast(f32)color.a / 255 };
set_mat4f(font_shader, "projection", zephr_ctx.projection)
rect: Rect
apply_constraints(&constraints, &rect.pos, &rect.size);
text_size := calculate_text_size(text, FONT_PIXEL_SIZE);
font_scale := cast(f32)font_size / FONT_PIXEL_SIZE * rect.size.x;
apply_alignment(alignment, &constraints, Vec2{ text_size.x * font_scale, text_size.y * font_scale }, &rect.pos);
model := identity()
scale(&model, Vec3{font_scale, font_scale, 1});
// rotate around the center point of the text
translate(&model, Vec3{-text_size.x * font_scale / 2, -text_size.y * font_scale / 2, 0});
rotate(&model, constraints.rotation, Vec3{0, 0, 1});
translate(&model, Vec3{text_size.x *font_scale / 2, text_size.y * font_scale / 2, 0});
translate(&model, Vec3{rect.pos.x, rect.pos.y, 0});
max_bearing_h: f32 = 0;
for i in 0..<len(text) {
ch := zephr_ctx.font.characters[text[i]];
max_bearing_h = max(max_bearing_h, ch.bearing.y);
}
first_char_bearing_w := zephr_ctx.font.characters[text[0]].bearing.x;
glyph_instance_list: GlyphInstanceList
reserve(&glyph_instance_list, 16)
// we use the original text and character sizes in the loop and then we just
// scale up or down the model matrix to get the desired font size.
// this way everything works out fine and we get to transform the text using the
// model matrix
c := 0;
x: u32 = 0;
y: f32 = 0;
for c != len(text) {
ch := zephr_ctx.font.characters[text[c]]
// subtract the bearing width of the first character to remove the extra space
// at the start of the text and move every char to the left by that width
xpos := (cast(f32)x + (ch.bearing.x - first_char_bearing_w));
ypos := y + (text_size.y - ch.bearing.y - (text_size.y - max_bearing_h));
if (text[c] == '\n') {
x = 0;
y += max_bearing_h + (36 * LINE_HEIGHT);
c += 1;
continue;
}
test_model := Mat4 {
1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12,
13, 14, 15, 16,
}
instance := GlyphInstance {
pos = Vec4{xpos, ypos, ch.size.x, ch.size.y},
tex_coords_idx = cast(u32)text[c] - 32,
color = text_color,
model = model,
}
append(&glyph_instance_list, instance)
x += (ch.advance >> 6);
c += 1;
}
return glyph_instance_list;
}
add_text_instance :: proc(batch: ^GlyphInstanceList, text: string, font_size: u32, constraints: UiConstraints, color: Color, alignment: Alignment) {
glyph_instance_list := get_glyph_instance_list_from_text(text, font_size, constraints, color, alignment);
append(batch, ..glyph_instance_list[:])
delete(glyph_instance_list);
}
+45
View File
@@ -0,0 +1,45 @@
package zephr
import gl "vendor:OpenGL"
import stb "vendor:stb/image"
TextureId :: u32
load_texture :: proc(path: cstring) -> TextureId {
texture_id: TextureId;
width, height, channels: i32
data := stb.load(path, &width, &height, &channels, 4) // RGBA
defer stb.image_free(data)
format := gl.RGBA;
switch (channels) {
case 1:
format = gl.RED;
break;
case 2:
format = gl.RG;
break;
case 3:
format = gl.RGB;
break;
case 4:
format = gl.RGBA;
break;
}
gl.GenTextures(1, &texture_id);
gl.BindTexture(gl.TEXTURE_2D, texture_id);
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.TexImage2D(gl.TEXTURE_2D, 0, cast(i32)format, width, height, 0, cast(u32)format, gl.UNSIGNED_BYTE, data);
gl.GenerateMipmap(gl.TEXTURE_2D);
return texture_id;
}
+35
View File
@@ -0,0 +1,35 @@
package zephr
import "core:fmt"
import "core:time"
TimerState :: enum {
RUNNING,
PAUSED,
STOPPED,
}
Timer :: struct {
start: f64,
elapsed: f64,
duration: f32,
state: TimerState,
}
@private
TIMER : time.Stopwatch
@private
start_internal_timer :: proc() {
time.stopwatch_start(&TIMER)
}
get_time :: proc() -> time.Duration {
return time.stopwatch_duration(TIMER)
}
start_timer :: proc(timer: ^time.Stopwatch) {
time.stopwatch_start(timer)
}
// TODO: incomplete
+938
View File
@@ -0,0 +1,938 @@
package zephr
import "core:fmt"
import "core:os"
import "core:mem"
import gl "vendor:OpenGL"
UiIdHash :: u32
Alignment :: enum {
TOP_LEFT,
TOP_CENTER,
TOP_RIGHT,
LEFT_CENTER,
CENTER,
RIGHT_CENTER,
BOTTOM_LEFT,
BOTTOM_CENTER,
BOTTOM_RIGHT,
}
UiConstraint :: enum {
FIXED,
RELATIVE,
RELATIVE_PIXELS,
ASPECT_RATIO,
}
ButtonState :: enum {
ACTIVE,
INACTIVE,
DISABLED,
}
UiConstraints :: struct {
x: f32,
y: f32,
width: f32,
height: f32,
rotation: f32,
scale: Vec2,
parent: ^UiConstraints,
}
Rect :: struct {
pos: Vec2,
size: Vec2,
}
UiStyle :: struct {
bg_color: Color,
fg_color: Color,
border_radius: f32,
align: Alignment,
}
Ui :: struct {
active_element: UiIdHash,
popup_open: bool,
popup_parent_hash: UiIdHash,
popup_rect: Rect,
popup_parent_constraints: UiConstraints,
popup_revert_color: ^Color,
}
@private
ui_shader: Shader
@private
color_chooser_shader: Shader
@private
ui_vao: u32
@private
ui_vbo: u32
DEFAULT_UI_CONSTRAINTS :: UiConstraints{
x = 0,
y = 0,
width = 0,
height = 0,
rotation = 0,
scale = Vec2{1, 1},
parent = nil,
}
@private
ui_init :: proc(font_path: cstring) {
res := init_fonts(font_path);
if (res == -1) {
fmt.printf("[ERROR]: could not initialize freetype library\n");
os.exit(1)
} else if (res == -2) {
fmt.printf("[ERROR]: could not load font file: \"%s\"\n", font_path);
os.exit(1)
} else if (res != 0) {
os.exit(1)
}
l_ui_shader, success1 := create_shader("shaders/ui.vert", "shaders/ui.frag")
l_color_chooser_shader, success2 := create_shader("shaders/ui.vert", "shaders/color_chooser.frag")
ui_shader = l_ui_shader
color_chooser_shader = l_color_chooser_shader
if !success1 || !success2 {
fmt.printf("[ERROR]: could not load ui shaders\n");
os.exit(1)
}
gl.GenVertexArrays(1, &ui_vao);
gl.GenBuffers(1, &ui_vbo);
gl.BindVertexArray(ui_vao);
gl.BindBuffer(gl.ARRAY_BUFFER, ui_vbo);
gl.BufferData(gl.ARRAY_BUFFER, size_of(f32) * 6 * 4, nil, gl.DYNAMIC_DRAW);
gl.EnableVertexAttribArray(0);
gl.VertexAttribPointer(0, 4, gl.FLOAT, gl.FALSE, 4 * size_of(f32), 0);
gl.BindBuffer(gl.ARRAY_BUFFER, 0);
gl.BindVertexArray(0);
}
set_parent_constraint :: proc(constraints: ^UiConstraints, parent_constraints: ^UiConstraints) {
constraints.parent = parent_constraints;
}
set_x_constraint :: proc(constraints: ^UiConstraints, value: f32, type: UiConstraint) {
switch (type) {
case .ASPECT_RATIO: // no-op. fall back to FIXED
fallthrough
case .FIXED:
constraints.x = value;
case .RELATIVE:
if (constraints.parent != nil) {
constraints.x = (value * constraints.parent.width);
} else {
constraints.x = (value * zephr_ctx.window.size.x);
}
case .RELATIVE_PIXELS:
constraints.x = zephr_ctx.window.size.x / zephr_ctx.screen_size.x * value;
}
if (constraints.parent != nil) {
constraints.x += constraints.parent.x;
}
}
set_y_constraint :: proc(constraints: ^UiConstraints, value: f32, type: UiConstraint) {
switch (type) {
case .ASPECT_RATIO: // no-op. fall back to FIXED
fallthrough
case .FIXED:
constraints.y = value;
case .RELATIVE:
if (constraints.parent != nil) {
constraints.y = (value * constraints.parent.height);
} else {
constraints.y = (value * zephr_ctx.window.size.y);
}
case .RELATIVE_PIXELS:
constraints.y = zephr_ctx.window.size.y / zephr_ctx.screen_size.y * value;
}
if (constraints.parent != nil) {
constraints.y += constraints.parent.y;
}
}
set_width_constraint :: proc(constraints: ^UiConstraints, value: f32, type: UiConstraint) {
switch (type) {
case .FIXED:
constraints.width = value;
case .RELATIVE:
if (constraints.parent != nil) {
constraints.width = (value * constraints.parent.width);
} else {
constraints.width = (value * zephr_ctx.window.size.x);
}
case .RELATIVE_PIXELS:
constraints.width = zephr_ctx.window.size.x / zephr_ctx.screen_size.x * value;
case .ASPECT_RATIO:
constraints.width = constraints.height * value;
}
}
set_height_constraint :: proc(constraints: ^UiConstraints, value: f32, type: UiConstraint) {
switch (type) {
case .FIXED:
constraints.height = value;
case .RELATIVE:
if (constraints.parent != nil) {
constraints.height = (value * constraints.parent.height);
} else {
constraints.height = (value * zephr_ctx.window.size.y);
}
case .RELATIVE_PIXELS:
constraints.height = zephr_ctx.window.size.y / zephr_ctx.screen_size.y * value;
case .ASPECT_RATIO:
constraints.height = constraints.width * value;
}
}
set_rotation_constraint :: proc(constraints: ^UiConstraints, angle_d: f32) {
constraints.rotation = angle_d;
}
@private
apply_constraints :: proc(constraints: ^UiConstraints, pos: ^Vec2, size: ^Vec2) {
pos^ = Vec2{constraints.x, constraints.y};
size^ = Vec2{constraints.width, constraints.height};
}
@private
apply_alignment :: proc(align: Alignment, constraints: ^UiConstraints, size: Vec2, pos: ^Vec2) {
// if we don't have a parent then we're a top level element and we should align against the window
parent_size := Vec2{zephr_ctx.window.size.x, zephr_ctx.window.size.y};
if (constraints.parent != nil) {
parent_size = Vec2{constraints.parent.width, constraints.parent.height};
}
switch (align) {
case .TOP_LEFT:
pos.x = pos.x;
pos.y = pos.y;
case .TOP_CENTER:
pos.x += parent_size.x / 2 - size.x / 2;
pos.y = pos.y;
case .TOP_RIGHT:
pos.x += parent_size.x - size.x;
pos.y = pos.y;
case .BOTTOM_LEFT:
pos.x = pos.x;
pos.y += parent_size.y - size.y;
case .BOTTOM_CENTER:
pos.x += parent_size.x / 2 - size.x / 2;
pos.y += parent_size.y - size.y;
case .BOTTOM_RIGHT:
pos.x += parent_size.x - size.x;
pos.y += parent_size.y - size.y;
case .LEFT_CENTER:
pos.x = pos.x;
pos.y += parent_size.y / 2 - size.y / 2;
case .RIGHT_CENTER:
pos.x += parent_size.x - size.x;
pos.y += parent_size.y / 2 - size.y / 2;
case .CENTER:
pos.x += parent_size.x / 2 - size.x / 2;
pos.y += parent_size.y / 2 - size.y / 2;
}
}
@private
// TODO: make this handle rotated and scaled rects too
inside_rect :: proc(rect: Rect, point: Vec2) -> bool {
return point.x >= rect.pos.x && point.x <= rect.pos.x + rect.size.x &&
point.y >= rect.pos.y && point.y <= rect.pos.y + rect.size.y;
}
draw_quad :: proc(constraints: ^UiConstraints, style: UiStyle) {
use_shader(ui_shader)
set_vec4f(ui_shader, "aColor", cast(f32)style.bg_color.r / 255, cast(f32)style.bg_color.g / 255, cast(f32)style.bg_color.b / 255, cast(f32)style.bg_color.a / 255)
set_float(ui_shader, "uiWidth", constraints.width)
set_float(ui_shader, "uiHeight", constraints.height)
set_float(ui_shader, "borderRadius", style.border_radius)
set_mat4f(ui_shader, "projection", zephr_ctx.projection)
rect: Rect = ---
apply_constraints(constraints, &rect.pos, &rect.size)
apply_alignment(style.align, constraints, rect.size, &rect.pos)
// set the positions after applying alignment so children can use them
constraints.x = rect.pos.x;
constraints.y = rect.pos.y;
model := identity();
translate(&model, Vec3{-rect.size.x / 2, -rect.size.y / 2, 0});
scale(&model, Vec3{constraints.scale.x, constraints.scale.y, 1});
translate(&model, Vec3{rect.size.x / 2, rect.size.y / 2, 0});
translate(&model, Vec3{-rect.size.x / 2, -rect.size.y / 2, 0});
rotate(&model, constraints.rotation, Vec3{0, 0, 1});
translate(&model, Vec3{rect.size.x / 2, rect.size.y / 2, 0});
translate(&model, Vec3{rect.pos.x, rect.pos.y, 0});
set_mat4f(ui_shader, "model", model);
gl.BindVertexArray(ui_vao)
vertices: [6][4]f32 = {
// bottom left tri
{0, 0 + rect.size.y, 0.0, 1.0},
{0, 0, 0.0, 0.0},
{0 + rect.size.x, 0, 1.0, 0.0},
// top right tri
{0, 0 + rect.size.y, 0.0, 1.0},
{0 + rect.size.x, 0, 1.0, 0.0},
{0 + rect.size.x, 0 + rect.size.y, 1.0, 1.0},
};
gl.BindBuffer(gl.ARRAY_BUFFER, ui_vbo);
gl.BufferSubData(gl.ARRAY_BUFFER, 0, size_of(vertices), raw_data(&vertices));
gl.BindBuffer(gl.ARRAY_BUFFER, 0);
gl.DrawArrays(gl.TRIANGLES, 0, 6);
gl.BindVertexArray(0);
}
draw_circle :: proc(constraints: ^UiConstraints, style: UiStyle) {
radius: f32 = 0;
if (constraints.width > constraints.height) {
radius = constraints.height / 2;
} else {
radius = constraints.width / 2;
}
new_style := style;
new_style.border_radius = radius;
draw_quad(constraints, new_style);
}
draw_triangle :: proc(constraints: ^UiConstraints, style: UiStyle) {
use_shader(ui_shader);
set_vec4f(ui_shader, "aColor", cast(f32)style.bg_color.r / 255, cast(f32)style.bg_color.g / 255, cast(f32)style.bg_color.b / 255, cast(f32)style.bg_color.a / 255);
set_float(ui_shader, "borderRadius", 0);
set_mat4f(ui_shader, "projection", zephr_ctx.projection)
rect: Rect = ---
apply_constraints(constraints, &rect.pos, &rect.size);
apply_alignment(style.align, constraints, rect.size, &rect.pos);
// set the positions after applying alignment so children can use them
constraints.x = rect.pos.x;
constraints.y = rect.pos.y;
model := identity();
translate(&model, Vec3{-rect.size.x / 2, -rect.size.y / 2, 0});
scale(&model, Vec3{constraints.scale.x, constraints.scale.y, 1});
translate(&model, Vec3{rect.size.x / 2, rect.size.y / 2, 0});
translate(&model, Vec3{-rect.size.x / 2, -rect.size.y / 2, 0});
rotate(&model, constraints.rotation, Vec3{0, 0, 1});
translate(&model, Vec3{rect.size.x / 2, rect.size.y / 2, 0});
translate(&model, Vec3{rect.pos.x, rect.pos.y, 0});
set_mat4f(ui_shader, "model", model);
gl.BindVertexArray(ui_vao);
vertices := [3][4]f32 {
{0 + rect.size.x, 0, 0.0, 0.0},
{0, 0, 0.0, 0.0},
{0 + rect.size.x / 2, 0 + rect.size.y, 0.0, 0.0},
};
gl.BindBuffer(gl.ARRAY_BUFFER, ui_vbo);
gl.BufferSubData(gl.ARRAY_BUFFER, 0, size_of(vertices), raw_data(&vertices))
gl.BindBuffer(gl.ARRAY_BUFFER, 0);
gl.DrawArrays(gl.TRIANGLES, 0, 3);
gl.BindVertexArray(0);
}
draw_texture :: proc(constraints: ^UiConstraints, texture_id: TextureId, color: Color, radius: f32, align: Alignment) {
use_shader(ui_shader);
set_vec4f(ui_shader, "aColor", cast(f32)color.r / 255, cast(f32)color.g / 255, cast(f32)color.b / 255, cast(f32)color.a / 255);
set_float(ui_shader, "uiWidth", constraints.width);
set_float(ui_shader, "uiHeight", constraints.height);
set_float(ui_shader, "borderRadius", radius);
set_bool(ui_shader, "hasTexture", true);
set_mat4f(ui_shader, "projection", zephr_ctx.projection)
rect: Rect = ---
apply_constraints(constraints, &rect.pos, &rect.size);
apply_alignment(align, constraints, rect.size, &rect.pos);
// set the positions after applying alignment so children can use them
constraints.x = rect.pos.x;
constraints.y = rect.pos.y;
model := identity();
translate(&model, Vec3{-rect.size.x / 2, -rect.size.y / 2, 0});
scale(&model, Vec3{constraints.scale.x, constraints.scale.y, 1});
translate(&model, Vec3{rect.size.x / 2, rect.size.y / 2, 0});
translate(&model, Vec3{-rect.size.x / 2, -rect.size.y / 2, 0});
rotate(&model, constraints.rotation, Vec3{0, 0, 1});
translate(&model, Vec3{rect.size.x / 2, rect.size.y / 2, 0});
translate(&model, Vec3{rect.pos.x, rect.pos.y, 0});
set_mat4f(ui_shader, "model", model)
gl.BindVertexArray(ui_vao);
vertices := [6][4]f32 {
// bottom left tri
{0, 0 + rect.size.y, 0.0, 1.0},
{0, 0, 0.0, 0.0},
{0 + rect.size.x, 0, 1.0, 0.0},
// top right tri
{0, 0 + rect.size.y, 0.0, 1.0},
{0 + rect.size.x, 0, 1.0, 0.0},
{0 + rect.size.x, 0 + rect.size.y, 1.0, 1.0},
};
gl.BindBuffer(gl.ARRAY_BUFFER, ui_vbo);
gl.BufferSubData(gl.ARRAY_BUFFER, 0, size_of(vertices), raw_data(&vertices))
gl.BindBuffer(gl.ARRAY_BUFFER, 0);
gl.ActiveTexture(gl.TEXTURE0);
gl.BindTexture(gl.TEXTURE_2D, texture_id);
gl.DrawArrays(gl.TRIANGLES, 0, 6);
gl.BindVertexArray(0);
set_bool(ui_shader, "hasTexture", false);
}
draw_button :: proc(constraints: ^UiConstraints, text: string, style: UiStyle, state: ButtonState, caller := #caller_location) -> bool {
line := caller.line
line_bytes := mem.byte_slice(&line, size_of(line))
hash := fnv_hash32(transmute([]byte)caller.file_path, cast(u32)len(caller.file_path), FNV_HASH32_INIT)
hash = fnv_hash32(line_bytes, size_of(caller.line), hash)
style := style
rect: Rect = ---
apply_constraints(constraints, &rect.pos, &rect.size);
apply_alignment(style.align, constraints, rect.size, &rect.pos);
is_hovered := inside_rect(rect, zephr_ctx.mouse.pos);
is_held := zephr_ctx.ui.active_element == hash
left_mouse_pressed := zephr_ctx.mouse.pressed && zephr_ctx.mouse.button == .BUTTON_LEFT;
left_mouse_released := zephr_ctx.mouse.released && zephr_ctx.mouse.button == .BUTTON_LEFT;
clicked := false;
if (zephr_ctx.ui.active_element == 0) {
if (is_hovered && left_mouse_pressed) {
zephr_ctx.ui.active_element = hash;
}
} else if (zephr_ctx.ui.active_element == hash) {
if (left_mouse_released) {
zephr_ctx.ui.active_element = 0;
if (is_hovered) {
clicked = true;
}
}
}
if (is_hovered && state == .ACTIVE) {
set_cursor(Cursor.HAND);
if (is_held) {
style.bg_color = mult_color(style.bg_color, 0.8);
draw_quad(constraints, style);
} else {
style.bg_color = mult_color(style.bg_color, 0.9);
draw_quad(constraints, style);
}
} else if (state == .DISABLED) {
if (is_hovered) {
set_cursor(Cursor.DISABLED);
}
style.bg_color.a = 100;
style.fg_color.a = 100;
draw_quad(constraints, style);
} else {
draw_quad(constraints, style);
}
if (text != "") {
text_constraints := DEFAULT_UI_CONSTRAINTS
set_parent_constraint(&text_constraints, constraints);
set_x_constraint(&text_constraints, 0, .RELATIVE_PIXELS);
set_y_constraint(&text_constraints, 0, .RELATIVE_PIXELS);
set_width_constraint(&text_constraints, 1.0, .RELATIVE_PIXELS);
font_size := ((constraints.width) / (text_constraints.width * 0.1 * 65));
// TODO: need to adjust the font size based on the text height
draw_text(text, cast(u32)font_size, text_constraints, style.fg_color, .CENTER);
}
if (clicked && state == .ACTIVE) {
return true;
}
return false;
}
draw_icon_button :: proc(constraints: ^UiConstraints, icon_tex_id: TextureId, style: UiStyle, state: ButtonState, caller := #caller_location) -> bool {
assert(icon_tex_id != 0, "draw_icon_button() requires that you provide an icon texture");
line := caller.line
line_bytes := mem.byte_slice(&line, size_of(line))
hash := fnv_hash32(transmute([]byte)caller.file_path, cast(u32)len(caller.file_path), FNV_HASH32_INIT)
hash = fnv_hash32(line_bytes, size_of(caller.line), hash)
style := style
rect: Rect = ---
apply_constraints(constraints, &rect.pos, &rect.size);
apply_alignment(style.align, constraints, rect.size, &rect.pos);
is_held := zephr_ctx.ui.active_element == hash;
is_hovered := inside_rect(rect, zephr_ctx.mouse.pos);
left_mouse_pressed := zephr_ctx.mouse.pressed && zephr_ctx.mouse.button == .BUTTON_LEFT;
left_mouse_released := zephr_ctx.mouse.released && zephr_ctx.mouse.button == .BUTTON_LEFT;
clicked := false;
if (zephr_ctx.ui.active_element == 0) {
if (is_hovered && left_mouse_pressed) {
zephr_ctx.ui.active_element = hash;
}
} else if (zephr_ctx.ui.active_element == hash) {
if (left_mouse_released) {
zephr_ctx.ui.active_element = 0;
if (is_hovered) {
clicked = true;
}
}
}
if (is_hovered && state == .ACTIVE) {
set_cursor(.HAND);
if (is_held) {
style.bg_color = mult_color(style.bg_color, 0.8);
style.fg_color = mult_color(style.fg_color, 0.8);
} else {
style.bg_color = mult_color(style.bg_color, 0.9);
style.fg_color = mult_color(style.fg_color, 0.9);
}
} else if (state == .DISABLED) {
if (is_hovered) {
set_cursor(.DISABLED);
}
style.fg_color.a = 100;
style.bg_color.a = 100;
}
draw_quad(constraints, style);
icon_constraints := DEFAULT_UI_CONSTRAINTS
set_parent_constraint(&icon_constraints, constraints);
set_x_constraint(&icon_constraints, 0, .RELATIVE_PIXELS);
set_y_constraint(&icon_constraints, 0, .RELATIVE_PIXELS);
set_width_constraint(&icon_constraints, constraints.width * 0.8, .FIXED);
set_height_constraint(&icon_constraints, constraints.height * 0.8, .FIXED);
draw_texture(&icon_constraints, icon_tex_id, style.fg_color, 0, .CENTER);
if (clicked && state == .ACTIVE) {
return true;
}
return false;
}
@private
draw_color_picker_slider :: proc(constraints: ^UiConstraints, align: Alignment, caller := #caller_location) -> f32 {
@static
slider_selection: f32 = 0
@static
dragging := false
line := caller.line
line_bytes := mem.byte_slice(&line, size_of(line))
hash := fnv_hash32(transmute([]byte)caller.file_path, cast(u32)len(caller.file_path), FNV_HASH32_INIT);
hash = fnv_hash32(line_bytes, size_of(caller.line), hash);
use_shader(color_chooser_shader);
set_bool(color_chooser_shader, "isSlider", true);
set_mat4f(color_chooser_shader, "projection", zephr_ctx.projection);
rect: Rect = ---
apply_constraints(constraints, &rect.pos, &rect.size);
apply_alignment(align, constraints, rect.size, &rect.pos);
// set the positions after applying alignment so children can use them
constraints.x = rect.pos.x;
constraints.y = rect.pos.y;
is_hovered := inside_rect(rect, zephr_ctx.mouse.pos);
left_mouse_pressed := zephr_ctx.mouse.pressed && zephr_ctx.mouse.button == .BUTTON_LEFT;
left_mouse_released := zephr_ctx.mouse.released && zephr_ctx.mouse.button == .BUTTON_LEFT;
if (zephr_ctx.ui.active_element == 0) {
if (is_hovered && left_mouse_pressed) {
zephr_ctx.ui.active_element = hash;
dragging = true;
}
} else if (zephr_ctx.ui.active_element == hash) {
if (left_mouse_released) {
zephr_ctx.ui.active_element = 0;
dragging = false;
}
}
if (dragging) {
slider_selection = clamp((zephr_ctx.mouse.pos.y - constraints.y) / constraints.height, 0, 1);
}
model := identity();
translate(&model, Vec3{rect.pos.x, rect.pos.y, 0})
set_mat4f(color_chooser_shader, "model", model);
gl.BindVertexArray(ui_vao);
vertices := [6][4]f32 {
// bottom left tri
{0, 0 + rect.size.y, 0.0, 1.0},
{0, 0, 0.0, 0.0},
{0 + rect.size.x, 0, 1.0, 0.0},
// top right tri
{0, 0 + rect.size.y, 0.0, 1.0},
{0 + rect.size.x, 0, 1.0, 0.0},
{0 + rect.size.x, 0 + rect.size.y, 1.0, 1.0},
};
gl.BindBuffer(gl.ARRAY_BUFFER, ui_vbo);
gl.BufferSubData(gl.ARRAY_BUFFER, 0, size_of(vertices), raw_data(&vertices))
gl.BindBuffer(gl.ARRAY_BUFFER, 0);
gl.DrawArrays(gl.TRIANGLES, 0, 6);
gl.BindVertexArray(0);
set_bool(color_chooser_shader, "isSlider", false);
// draw the selection
triangle_con := DEFAULT_UI_CONSTRAINTS
set_parent_constraint(&triangle_con, constraints);
set_x_constraint(&triangle_con, -11, .RELATIVE_PIXELS);
set_y_constraint(&triangle_con, slider_selection, .RELATIVE);
set_rotation_constraint(&triangle_con, -90);
set_width_constraint(&triangle_con, 14, .RELATIVE_PIXELS);
set_height_constraint(&triangle_con, 0.5, .ASPECT_RATIO);
triangle_con.y += -(triangle_con.height / 2);
tri_style := UiStyle {
bg_color = COLOR_BLACK,
border_radius = 0,
align = .TOP_LEFT,
};
draw_triangle(&triangle_con, tri_style);
set_x_constraint(&triangle_con, 1, .RELATIVE);
triangle_con.x -= triangle_con.height / 2;
set_rotation_constraint(&triangle_con, 90);
draw_triangle(&triangle_con, tri_style);
return slider_selection;
}
@private
draw_color_picker_canvas :: proc(constraints: ^UiConstraints, slider_percentage: f32, align: Alignment, caller := #caller_location) -> Color {
@static
dragging := false;
@static
canvas_pos := Vec2{0, 0};
line := caller.line
line_bytes := mem.byte_slice(&line, size_of(line))
hash := fnv_hash32(transmute([]byte)caller.file_path, cast(u32)len(caller.file_path), FNV_HASH32_INIT);
hash = fnv_hash32(line_bytes, size_of(caller.line), hash);
use_shader(color_chooser_shader);
set_bool(color_chooser_shader, "isSlider", false);
set_float(color_chooser_shader, "sliderPercentage", slider_percentage);
set_mat4f(color_chooser_shader, "projection", zephr_ctx.projection);
rect: Rect = ---
apply_constraints(constraints, &rect.pos, &rect.size);
apply_alignment(align, constraints, rect.size, &rect.pos);
// set the positions after applying alignment so children can use them
constraints.x = rect.pos.x;
constraints.y = rect.pos.y;
is_hovered := inside_rect(rect, zephr_ctx.mouse.pos);
left_mouse_pressed := zephr_ctx.mouse.pressed && zephr_ctx.mouse.button == .BUTTON_LEFT;
left_mouse_released := zephr_ctx.mouse.released && zephr_ctx.mouse.button == .BUTTON_LEFT;
if (zephr_ctx.ui.active_element == 0) {
if (is_hovered && left_mouse_pressed) {
zephr_ctx.ui.active_element = hash;
dragging = true;
}
} else if (zephr_ctx.ui.active_element == hash) {
if (left_mouse_released) {
zephr_ctx.ui.active_element = 0;
dragging = false;
}
}
if (dragging) {
x := (zephr_ctx.mouse.pos.x - constraints.x) / constraints.width;
y := (zephr_ctx.mouse.pos.y - constraints.y) / constraints.height;
canvas_pos.x = clamp(x, 0, 1);
canvas_pos.y = clamp(y, 0, 1);
}
model := identity();
translate(&model, Vec3{rect.pos.x, rect.pos.y, 0});
set_mat4f(color_chooser_shader, "model", model);
gl.BindVertexArray(ui_vao);
vertices := [6][4]f32 {
// bottom left tri
{0, 0 + rect.size.y, 0.0, 1.0},
{0, 0, 0.0, 0.0},
{0 + rect.size.x, 0, 1.0, 0.0},
// top right tri
{0, 0 + rect.size.y, 0.0, 1.0},
{0 + rect.size.x, 0, 1.0, 0.0},
{0 + rect.size.x, 0 + rect.size.y, 1.0, 1.0},
};
gl.BindBuffer(gl.ARRAY_BUFFER, ui_vbo);
gl.BufferSubData(gl.ARRAY_BUFFER, 0, size_of(vertices), raw_data(&vertices))
gl.BindBuffer(gl.ARRAY_BUFFER, 0);
gl.DrawArrays(gl.TRIANGLES, 0, 6);
gl.BindVertexArray(0);
selected_color := hsv2rgb(slider_percentage * 360, canvas_pos.x, 1.0 - canvas_pos.y);
// draw the selection
triangle_con := DEFAULT_UI_CONSTRAINTS
set_parent_constraint(&triangle_con, constraints);
set_x_constraint(&triangle_con, canvas_pos.x, .RELATIVE);
set_y_constraint(&triangle_con, canvas_pos.y, .RELATIVE);
set_width_constraint(&triangle_con, 14, .RELATIVE_PIXELS);
set_height_constraint(&triangle_con, 0.5, .ASPECT_RATIO);
triangle_con.x -= triangle_con.height;
triangle_con.y -= triangle_con.height + triangle_con.height / 2 + triangle_con.width / 2;
set_rotation_constraint(&triangle_con, 0);
tri_style := UiStyle {
bg_color = determine_color_contrast(selected_color),
border_radius = 0,
align = .TOP_LEFT,
};
draw_triangle(&triangle_con, tri_style);
triangle_con.x += triangle_con.height * 2;
triangle_con.y += triangle_con.height * 2;
set_rotation_constraint(&triangle_con, 90);
draw_triangle(&triangle_con, tri_style);
triangle_con.x -= triangle_con.height * 2;
triangle_con.y += triangle_con.height * 2;
set_rotation_constraint(&triangle_con, 180);
draw_triangle(&triangle_con, tri_style);
triangle_con.x -= triangle_con.height * 2;
triangle_con.y -= triangle_con.height * 2;
set_rotation_constraint(&triangle_con, 270);
draw_triangle(&triangle_con, tri_style);
return selected_color;
}
// TODO: consume mouse events on the popup
@private
draw_color_picker_popup :: proc(picker_button_con: ^UiConstraints) {
popup_con := DEFAULT_UI_CONSTRAINTS
set_parent_constraint(&popup_con, picker_button_con);
set_x_constraint(&popup_con, 0, .FIXED);
set_y_constraint(&popup_con, 1.4, .RELATIVE);
set_width_constraint(&popup_con, 450, .RELATIVE_PIXELS);
set_height_constraint(&popup_con, 480, .RELATIVE_PIXELS);
popup_style := UiStyle {
bg_color = mult_color(COLOR_WHITE, 0.94),
border_radius = popup_con.width * 0.02,
align = .TOP_LEFT,
};
draw_quad(&popup_con, popup_style);
color_slider_con := DEFAULT_UI_CONSTRAINTS
set_parent_constraint(&color_slider_con, &popup_con);
set_x_constraint(&color_slider_con, -0.04, .RELATIVE);
set_y_constraint(&color_slider_con, 0.05, .RELATIVE);
set_height_constraint(&color_slider_con, 400 * 0.9, .RELATIVE_PIXELS);
set_width_constraint(&color_slider_con, 0.1, .ASPECT_RATIO);
slider_selection := draw_color_picker_slider(&color_slider_con, .TOP_RIGHT);
set_x_constraint(&color_slider_con, 0.04, .RELATIVE);
set_y_constraint(&color_slider_con, 0.05, .RELATIVE);
set_width_constraint(&color_slider_con, popup_con.width * 0.8, .FIXED);
selected_color := draw_color_picker_canvas(&color_slider_con, slider_selection, .TOP_LEFT);
button_con := DEFAULT_UI_CONSTRAINTS
set_parent_constraint(&button_con, &popup_con);
set_x_constraint(&button_con, 0.04, .RELATIVE);
set_y_constraint(&button_con, -0.05, .RELATIVE);
set_width_constraint(&button_con, 0.43, .RELATIVE);
set_height_constraint(&button_con, 0.3, .ASPECT_RATIO);
button_style := UiStyle {
bg_color = zephr_ctx.ui.popup_revert_color^,
fg_color = determine_color_contrast(zephr_ctx.ui.popup_revert_color^),
border_radius = 4,
align = .BOTTOM_LEFT,
}
if (draw_button(&button_con, "Revert", button_style, .ACTIVE)) {
zephr_ctx.ui.popup_open = false;
zephr_ctx.ui.popup_parent_hash = 0;
}
button_style.align = .BOTTOM_RIGHT;
button_style.fg_color = determine_color_contrast(selected_color);
set_x_constraint(&button_con, -0.04, .RELATIVE);
set_y_constraint(&button_con, -0.05, .RELATIVE);
button_style.bg_color = selected_color;
if (draw_button(&button_con, "Apply", button_style, .ACTIVE)) {
zephr_ctx.ui.popup_revert_color^ = selected_color;
zephr_ctx.ui.popup_open = false;
zephr_ctx.ui.popup_parent_hash = 0;
}
rect := Rect {
{popup_con.x, popup_con.y},
{popup_con.width, popup_con.height},
};
zephr_ctx.ui.popup_rect = rect;
}
draw_color_picker :: proc(constraints: ^UiConstraints, color: ^Color, align: Alignment, state: ButtonState, id: u32 = 0, caller := #caller_location) {
line := caller.line
line_bytes := mem.byte_slice(&line, size_of(line))
id := id
id_bytes := mem.byte_slice(&id, size_of(id))
hash := fnv_hash32(id_bytes, size_of(id), FNV_HASH32_INIT);
hash = fnv_hash32(transmute([]byte)caller.file_path, cast(u32)len(caller.file_path), hash);
hash = fnv_hash32(line_bytes, size_of(line), hash);
rect: Rect = ---
display_color := color^;
con := constraints^;
apply_constraints(&con, &rect.pos, &rect.size);
apply_alignment(align, &con, rect.size, &rect.pos);
is_held := zephr_ctx.ui.active_element == hash
is_hovered := inside_rect(rect, zephr_ctx.mouse.pos);
left_mouse_pressed := zephr_ctx.mouse.pressed && zephr_ctx.mouse.button == .BUTTON_LEFT;
left_mouse_released := zephr_ctx.mouse.released && zephr_ctx.mouse.button == .BUTTON_LEFT;
clicked := false;
if (zephr_ctx.ui.active_element == 0) {
if (is_hovered && left_mouse_pressed) {
zephr_ctx.ui.active_element = hash;
}
} else if (zephr_ctx.ui.active_element == hash) {
if (left_mouse_released) {
zephr_ctx.ui.active_element = 0;
if (is_hovered) {
clicked = true;
}
}
}
if (is_hovered && state == .ACTIVE) {
set_cursor(.HAND);
con.scale.y *= 1.08;
con.scale.x *= 1.08;
if (is_held) {
display_color = mult_color(display_color, 0.8);
} else {
display_color = mult_color(display_color, 0.9);
}
} else if (state == .DISABLED) {
if (is_hovered) {
set_cursor(.DISABLED);
}
display_color.a = 100;
}
style := UiStyle {
bg_color = display_color,
border_radius = con.width * 0.08,
align = align,
};
draw_quad(&con, style);
if (clicked && state == .ACTIVE) {
zephr_ctx.ui.popup_open = true;
zephr_ctx.ui.popup_parent_constraints = con;
zephr_ctx.ui.popup_revert_color = color;
zephr_ctx.ui.popup_parent_hash = hash;
}
if (zephr_ctx.ui.popup_parent_hash == hash) {
zephr_ctx.ui.popup_open = true;
zephr_ctx.ui.popup_parent_constraints = con;
}
/* if (is_hovered && state == BUTTON_STATE_ACTIVE && zephr_ctx->mouse.released && zephr_ctx->mouse.button == ZEPHR_MOUSE_BUTTON_LEFT) { */
/* return true; */
/* } */
/* return false; */
}
+1250
View File
File diff suppressed because it is too large Load Diff
+147
View File
@@ -0,0 +1,147 @@
package zephr
import "core:math"
Vec2 :: [2]f32
Vec3 :: [3]f32
Vec4 :: [4]f32
Mat3 :: matrix[3, 3]f32
Mat4 :: matrix[4, 4]f32
Color :: struct { r, g, b, a: u8 }
orthographic_projection_2d :: proc(left, right, bottom, top: f32) -> Mat4 {
result := identity();
result[0][0] = 2 / (right - left);
result[3][0] = -(right + left) / (right - left);
result[1][1] = 2 / (top - bottom);
result[3][1] = -(top + bottom) / (top - bottom);
result[2][2] = -1;
result[3][3] = 1;
return result;
}
identity :: proc() -> Mat4 {
return Mat4 {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
}
}
translate :: proc(m: ^Mat4, v: Vec3) {
temp := identity()
temp[3][0] = v.x
temp[3][1] = v.y
temp[3][2] = v.z
m^ = temp * m^
}
rotate :: proc(m: ^Mat4, angle: f32, axis: Vec3) {
temp := identity()
c := math.cos(math.to_radians(angle))
s := math.sin(math.to_radians(angle))
t := 1 - c
x, y, z := axis.x, axis.y, axis.z
res := Mat4{
t*x*x + c, t*x*y - s*z, t*x*z + s*y, 0,
t*x*y + s*z, t*y*y + c, t*y*z - s*x, 0,
t*x*z - s*y, t*y*z + s*x, t*z*z + c, 0,
0 , 0, 0, 1,
}
m^ = res * m^
}
scale :: proc(m: ^Mat4, v: Vec3) {
temp := identity()
temp[0][0] = v.x
temp[1][1] = v.y
temp[2][2] = v.z
m^ = temp * m^
}
mult_color :: proc(color: Color, scalar: f32) -> Color {
color := color
color.r = clamp(cast(u8)(cast(f32)color.r * scalar), 0, 255)
color.g = clamp(cast(u8)(cast(f32)color.g * scalar), 0, 255)
color.b = clamp(cast(u8)(cast(f32)color.b * scalar), 0, 255)
return color
}
hsv2rgb :: proc(h: f32, s: f32, v: f32) -> Color {
c := v * s;
x := (c * (1 - abs(math.mod(h / 60.0, 2) - 1)));
m := v - c;
r, g, b: f32
if (h >= 0 && h < 60) {
r = c
g = x
b = 0
} else if (h >= 60 && h < 120) {
r = x
g = c
b = 0
} else if (h >= 120 && h < 180) {
r = 0
g = c
b = x
} else if (h >= 180 && h < 240) {
r = 0
g = x
b = c
} else if (h >= 240 && h < 300) {
r = x
g = 0
b = c
} else {
r = c
g = 0
b = x;
}
return (Color){(u8)((r + m) * 255), (u8)((g + m) * 255), (u8)((b + m) * 255), 255};
}
determine_color_contrast :: proc(bg: Color) -> Color {
white_contrast := get_contrast(bg, COLOR_WHITE);
black_contrast := get_contrast(bg, COLOR_BLACK);
return white_contrast > black_contrast ? COLOR_WHITE : COLOR_BLACK;
}
@private
get_srgb :: proc(component: f32) -> f32 {
return (component / 255 <= 0.03928
? component / 255 / 12.92
: math.pow((component / 255 + 0.055) / 1.055, 2.4));
}
get_luminance :: proc(color: Color) -> f32 {
return ((0.2126 * get_srgb(cast(f32)color.r)) +
(0.7152 * get_srgb(cast(f32)color.g)) +
(0.0722 * get_srgb(cast(f32)color.b))) / 255;
}
@private
get_contrast :: proc(fg: Color, bg: Color) -> f32 {
l1 := get_luminance(fg)
l2 := get_luminance(bg)
return (max(l1, l2) + 0.05) / (min(l1, l2) + 0.05);
}