2024-01-19 23:55:07 +02:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
UiElement :: struct {
|
|
|
|
|
id: UiIdHash,
|
|
|
|
|
rect: Rect,
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-19 23:55:07 +02:00
|
|
|
Ui :: struct {
|
2024-01-20 06:39:49 +02:00
|
|
|
hovered_element: UiIdHash,
|
2024-01-19 23:55:07 +02:00
|
|
|
active_element: UiIdHash,
|
2024-01-20 06:39:49 +02:00
|
|
|
elements: [dynamic]UiElement,
|
2024-01-19 23:55:07 +02:00
|
|
|
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) {
|
2024-01-20 06:39:49 +02:00
|
|
|
res := init_fonts(font_path)
|
2024-01-19 23:55:07 +02:00
|
|
|
if (res == -1) {
|
2024-01-20 06:39:49 +02:00
|
|
|
fmt.eprintf("[ERROR]: could not initialize freetype library\n")
|
2024-01-19 23:55:07 +02:00
|
|
|
os.exit(1)
|
|
|
|
|
} else if (res == -2) {
|
2024-01-20 06:39:49 +02:00
|
|
|
fmt.eprintf("[ERROR]: could not load font file: \"%s\"\n", font_path)
|
2024-01-19 23:55:07 +02:00
|
|
|
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 {
|
2024-01-20 06:39:49 +02:00
|
|
|
fmt.eprintf("[ERROR]: could not load ui shaders\n")
|
2024-01-19 23:55:07 +02:00
|
|
|
os.exit(1)
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.GenVertexArrays(1, &ui_vao)
|
|
|
|
|
gl.GenBuffers(1, &ui_vbo)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
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)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
|
|
|
|
gl.BindVertexArray(0)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set_parent_constraint :: proc(constraints: ^UiConstraints, parent_constraints: ^UiConstraints) {
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.parent = parent_constraints
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set_x_constraint :: proc(constraints: ^UiConstraints, value: f32, type: UiConstraint) {
|
|
|
|
|
switch (type) {
|
|
|
|
|
case .ASPECT_RATIO: // no-op. fall back to FIXED
|
|
|
|
|
fallthrough
|
|
|
|
|
case .FIXED:
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.x = value
|
2024-01-19 23:55:07 +02:00
|
|
|
case .RELATIVE:
|
|
|
|
|
if (constraints.parent != nil) {
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.x = (value * constraints.parent.width)
|
2024-01-19 23:55:07 +02:00
|
|
|
} else {
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.x = (value * zephr_ctx.window.size.x)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
case .RELATIVE_PIXELS:
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.x = zephr_ctx.window.size.x / zephr_ctx.screen_size.x * value
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (constraints.parent != nil) {
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.x += constraints.parent.x
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set_y_constraint :: proc(constraints: ^UiConstraints, value: f32, type: UiConstraint) {
|
|
|
|
|
switch (type) {
|
|
|
|
|
case .ASPECT_RATIO: // no-op. fall back to FIXED
|
|
|
|
|
fallthrough
|
|
|
|
|
case .FIXED:
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.y = value
|
2024-01-19 23:55:07 +02:00
|
|
|
case .RELATIVE:
|
|
|
|
|
if (constraints.parent != nil) {
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.y = (value * constraints.parent.height)
|
2024-01-19 23:55:07 +02:00
|
|
|
} else {
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.y = (value * zephr_ctx.window.size.y)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
case .RELATIVE_PIXELS:
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.y = zephr_ctx.window.size.y / zephr_ctx.screen_size.y * value
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (constraints.parent != nil) {
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.y += constraints.parent.y
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set_width_constraint :: proc(constraints: ^UiConstraints, value: f32, type: UiConstraint) {
|
|
|
|
|
switch (type) {
|
|
|
|
|
case .FIXED:
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.width = value
|
2024-01-19 23:55:07 +02:00
|
|
|
case .RELATIVE:
|
|
|
|
|
if (constraints.parent != nil) {
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.width = (value * constraints.parent.width)
|
2024-01-19 23:55:07 +02:00
|
|
|
} else {
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.width = (value * zephr_ctx.window.size.x)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
case .RELATIVE_PIXELS:
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.width = zephr_ctx.window.size.x / zephr_ctx.screen_size.x * value
|
2024-01-19 23:55:07 +02:00
|
|
|
case .ASPECT_RATIO:
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.width = constraints.height * value
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set_height_constraint :: proc(constraints: ^UiConstraints, value: f32, type: UiConstraint) {
|
|
|
|
|
switch (type) {
|
|
|
|
|
case .FIXED:
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.height = value
|
2024-01-19 23:55:07 +02:00
|
|
|
case .RELATIVE:
|
|
|
|
|
if (constraints.parent != nil) {
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.height = (value * constraints.parent.height)
|
2024-01-19 23:55:07 +02:00
|
|
|
} else {
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.height = (value * zephr_ctx.window.size.y)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
case .RELATIVE_PIXELS:
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.height = zephr_ctx.window.size.y / zephr_ctx.screen_size.y * value
|
2024-01-19 23:55:07 +02:00
|
|
|
case .ASPECT_RATIO:
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.height = constraints.width * value
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set_rotation_constraint :: proc(constraints: ^UiConstraints, angle_d: f32) {
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.rotation = angle_d
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@private
|
|
|
|
|
apply_constraints :: proc(constraints: ^UiConstraints, pos: ^Vec2, size: ^Vec2) {
|
2024-01-20 06:39:49 +02:00
|
|
|
pos^ = Vec2{constraints.x, constraints.y}
|
|
|
|
|
size^ = Vec2{constraints.width, constraints.height}
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@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
|
2024-01-20 06:39:49 +02:00
|
|
|
parent_size := Vec2{zephr_ctx.window.size.x, zephr_ctx.window.size.y}
|
2024-01-19 23:55:07 +02:00
|
|
|
if (constraints.parent != nil) {
|
2024-01-20 06:39:49 +02:00
|
|
|
parent_size = Vec2{constraints.parent.width, constraints.parent.height}
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (align) {
|
|
|
|
|
case .TOP_LEFT:
|
2024-01-20 06:39:49 +02:00
|
|
|
pos.x = pos.x
|
|
|
|
|
pos.y = pos.y
|
2024-01-19 23:55:07 +02:00
|
|
|
case .TOP_CENTER:
|
2024-01-20 06:39:49 +02:00
|
|
|
pos.x += parent_size.x / 2 - size.x / 2
|
|
|
|
|
pos.y = pos.y
|
2024-01-19 23:55:07 +02:00
|
|
|
case .TOP_RIGHT:
|
2024-01-20 06:39:49 +02:00
|
|
|
pos.x += parent_size.x - size.x
|
|
|
|
|
pos.y = pos.y
|
2024-01-19 23:55:07 +02:00
|
|
|
case .BOTTOM_LEFT:
|
2024-01-20 06:39:49 +02:00
|
|
|
pos.x = pos.x
|
|
|
|
|
pos.y += parent_size.y - size.y
|
2024-01-19 23:55:07 +02:00
|
|
|
case .BOTTOM_CENTER:
|
2024-01-20 06:39:49 +02:00
|
|
|
pos.x += parent_size.x / 2 - size.x / 2
|
|
|
|
|
pos.y += parent_size.y - size.y
|
2024-01-19 23:55:07 +02:00
|
|
|
case .BOTTOM_RIGHT:
|
2024-01-20 06:39:49 +02:00
|
|
|
pos.x += parent_size.x - size.x
|
|
|
|
|
pos.y += parent_size.y - size.y
|
2024-01-19 23:55:07 +02:00
|
|
|
case .LEFT_CENTER:
|
2024-01-20 06:39:49 +02:00
|
|
|
pos.x = pos.x
|
|
|
|
|
pos.y += parent_size.y / 2 - size.y / 2
|
2024-01-19 23:55:07 +02:00
|
|
|
case .RIGHT_CENTER:
|
2024-01-20 06:39:49 +02:00
|
|
|
pos.x += parent_size.x - size.x
|
|
|
|
|
pos.y += parent_size.y / 2 - size.y / 2
|
2024-01-19 23:55:07 +02:00
|
|
|
case .CENTER:
|
2024-01-20 06:39:49 +02:00
|
|
|
pos.x += parent_size.x / 2 - size.x / 2
|
|
|
|
|
pos.y += parent_size.y / 2 - size.y / 2
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@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 &&
|
2024-01-20 06:39:49 +02:00
|
|
|
point.y >= rect.pos.y && point.y <= rect.pos.y + rect.size.y
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.x = rect.pos.x
|
|
|
|
|
constraints.y = rect.pos.y
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
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})
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
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})
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
translate(&model, Vec3{rect.pos.x, rect.pos.y, 0})
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
set_mat4f(ui_shader, "model", model)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
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},
|
2024-01-20 06:39:49 +02:00
|
|
|
}
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
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)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.DrawArrays(gl.TRIANGLES, 0, 6)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.BindVertexArray(0)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
draw_circle :: proc(constraints: ^UiConstraints, style: UiStyle) {
|
2024-01-20 06:39:49 +02:00
|
|
|
radius: f32 = 0
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
if (constraints.width > constraints.height) {
|
2024-01-20 06:39:49 +02:00
|
|
|
radius = constraints.height / 2
|
2024-01-19 23:55:07 +02:00
|
|
|
} else {
|
2024-01-20 06:39:49 +02:00
|
|
|
radius = constraints.width / 2
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
new_style := style
|
|
|
|
|
new_style.border_radius = radius
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
draw_quad(constraints, new_style)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
draw_triangle :: proc(constraints: ^UiConstraints, style: UiStyle) {
|
2024-01-20 06:39:49 +02:00
|
|
|
use_shader(ui_shader)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
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)
|
2024-01-19 23:55:07 +02:00
|
|
|
set_mat4f(ui_shader, "projection", zephr_ctx.projection)
|
|
|
|
|
|
|
|
|
|
rect: Rect = ---
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
apply_constraints(constraints, &rect.pos, &rect.size)
|
|
|
|
|
apply_alignment(style.align, constraints, rect.size, &rect.pos)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
// set the positions after applying alignment so children can use them
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.x = rect.pos.x
|
|
|
|
|
constraints.y = rect.pos.y
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
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})
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
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})
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
translate(&model, Vec3{rect.pos.x, rect.pos.y, 0})
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
set_mat4f(ui_shader, "model", model)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.BindVertexArray(ui_vao)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
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},
|
2024-01-20 06:39:49 +02:00
|
|
|
}
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, ui_vbo)
|
2024-01-19 23:55:07 +02:00
|
|
|
gl.BufferSubData(gl.ARRAY_BUFFER, 0, size_of(vertices), raw_data(&vertices))
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.DrawArrays(gl.TRIANGLES, 0, 3)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.BindVertexArray(0)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
draw_texture :: proc(constraints: ^UiConstraints, texture_id: TextureId, color: Color, radius: f32, align: Alignment) {
|
2024-01-20 06:39:49 +02:00
|
|
|
use_shader(ui_shader)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
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)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
set_mat4f(ui_shader, "projection", zephr_ctx.projection)
|
|
|
|
|
|
|
|
|
|
rect: Rect = ---
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
apply_constraints(constraints, &rect.pos, &rect.size)
|
|
|
|
|
apply_alignment(align, constraints, rect.size, &rect.pos)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
// set the positions after applying alignment so children can use them
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.x = rect.pos.x
|
|
|
|
|
constraints.y = rect.pos.y
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
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})
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
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})
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
translate(&model, Vec3{rect.pos.x, rect.pos.y, 0})
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
set_mat4f(ui_shader, "model", model)
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.BindVertexArray(ui_vao)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
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},
|
2024-01-20 06:39:49 +02:00
|
|
|
}
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, ui_vbo)
|
2024-01-19 23:55:07 +02:00
|
|
|
gl.BufferSubData(gl.ARRAY_BUFFER, 0, size_of(vertices), raw_data(&vertices))
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.ActiveTexture(gl.TEXTURE0)
|
|
|
|
|
gl.BindTexture(gl.TEXTURE_2D, texture_id)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.DrawArrays(gl.TRIANGLES, 0, 6)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.BindVertexArray(0)
|
|
|
|
|
set_bool(ui_shader, "hasTexture", false)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 = ---
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
apply_constraints(constraints, &rect.pos, &rect.size)
|
|
|
|
|
apply_alignment(style.align, constraints, rect.size, &rect.pos)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
is_hovered := zephr_ctx.ui.hovered_element == hash
|
2024-01-19 23:55:07 +02:00
|
|
|
is_held := zephr_ctx.ui.active_element == hash
|
2024-01-20 06:39:49 +02:00
|
|
|
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
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
if (zephr_ctx.ui.active_element == 0) {
|
|
|
|
|
if (is_hovered && left_mouse_pressed) {
|
2024-01-20 06:39:49 +02:00
|
|
|
zephr_ctx.ui.active_element = hash
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
} else if (zephr_ctx.ui.active_element == hash) {
|
|
|
|
|
if (left_mouse_released) {
|
2024-01-20 06:39:49 +02:00
|
|
|
zephr_ctx.ui.active_element = 0
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
if (is_hovered) {
|
2024-01-20 06:39:49 +02:00
|
|
|
clicked = true
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_hovered && state == .ACTIVE) {
|
2024-01-20 06:39:49 +02:00
|
|
|
set_cursor(Cursor.HAND)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
if (is_held) {
|
2024-01-20 06:39:49 +02:00
|
|
|
style.bg_color = mult_color(style.bg_color, 0.8)
|
|
|
|
|
draw_quad(constraints, style)
|
2024-01-19 23:55:07 +02:00
|
|
|
} else {
|
2024-01-20 06:39:49 +02:00
|
|
|
style.bg_color = mult_color(style.bg_color, 0.9)
|
|
|
|
|
draw_quad(constraints, style)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
} else if (state == .DISABLED) {
|
|
|
|
|
if (is_hovered) {
|
2024-01-20 06:39:49 +02:00
|
|
|
set_cursor(Cursor.DISABLED)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
style.bg_color.a = 100
|
|
|
|
|
style.fg_color.a = 100
|
|
|
|
|
draw_quad(constraints, style)
|
2024-01-19 23:55:07 +02:00
|
|
|
} else {
|
2024-01-20 06:39:49 +02:00
|
|
|
draw_quad(constraints, style)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (text != "") {
|
|
|
|
|
text_constraints := DEFAULT_UI_CONSTRAINTS
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
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)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
font_size := ((constraints.width) / (text_constraints.width * 0.1 * 65))
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
// TODO: need to adjust the font size based on the text height
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
draw_text(text, cast(u32)font_size, text_constraints, style.fg_color, .CENTER)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
element := UiElement{
|
|
|
|
|
id = hash,
|
|
|
|
|
rect = rect,
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
append(&zephr_ctx.ui.elements, element)
|
|
|
|
|
|
2024-01-19 23:55:07 +02:00
|
|
|
if (clicked && state == .ACTIVE) {
|
2024-01-20 06:39:49 +02:00
|
|
|
return true
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
return false
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
draw_icon_button :: proc(constraints: ^UiConstraints, icon_tex_id: TextureId, style: UiStyle, state: ButtonState, caller := #caller_location) -> bool {
|
2024-01-20 06:39:49 +02:00
|
|
|
assert(icon_tex_id != 0, "draw_icon_button() requires that you provide an icon texture")
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
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 = ---
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
apply_constraints(constraints, &rect.pos, &rect.size)
|
|
|
|
|
apply_alignment(style.align, constraints, rect.size, &rect.pos)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
is_held := zephr_ctx.ui.active_element == hash
|
|
|
|
|
is_hovered := zephr_ctx.ui.hovered_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
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
if (zephr_ctx.ui.active_element == 0) {
|
|
|
|
|
if (is_hovered && left_mouse_pressed) {
|
2024-01-20 06:39:49 +02:00
|
|
|
zephr_ctx.ui.active_element = hash
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
} else if (zephr_ctx.ui.active_element == hash) {
|
|
|
|
|
if (left_mouse_released) {
|
2024-01-20 06:39:49 +02:00
|
|
|
zephr_ctx.ui.active_element = 0
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
if (is_hovered) {
|
2024-01-20 06:39:49 +02:00
|
|
|
clicked = true
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_hovered && state == .ACTIVE) {
|
2024-01-20 06:39:49 +02:00
|
|
|
set_cursor(.HAND)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
if (is_held) {
|
2024-01-20 06:39:49 +02:00
|
|
|
style.bg_color = mult_color(style.bg_color, 0.8)
|
|
|
|
|
style.fg_color = mult_color(style.fg_color, 0.8)
|
2024-01-19 23:55:07 +02:00
|
|
|
} else {
|
2024-01-20 06:39:49 +02:00
|
|
|
style.bg_color = mult_color(style.bg_color, 0.9)
|
|
|
|
|
style.fg_color = mult_color(style.fg_color, 0.9)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
} else if (state == .DISABLED) {
|
|
|
|
|
if (is_hovered) {
|
2024-01-20 06:39:49 +02:00
|
|
|
set_cursor(.DISABLED)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
style.fg_color.a = 100
|
|
|
|
|
style.bg_color.a = 100
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
draw_quad(constraints, style)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
icon_constraints := DEFAULT_UI_CONSTRAINTS
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
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)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
if (clicked && state == .ACTIVE) {
|
2024-01-20 06:39:49 +02:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
element := UiElement{
|
|
|
|
|
id = hash,
|
|
|
|
|
rect = rect
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
append(&zephr_ctx.ui.elements, element)
|
|
|
|
|
|
|
|
|
|
return false
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@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))
|
2024-01-20 06:39:49 +02:00
|
|
|
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)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
use_shader(color_chooser_shader)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
set_bool(color_chooser_shader, "isSlider", true)
|
|
|
|
|
set_mat4f(color_chooser_shader, "projection", zephr_ctx.projection)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
rect: Rect = ---
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
apply_constraints(constraints, &rect.pos, &rect.size)
|
|
|
|
|
apply_alignment(align, constraints, rect.size, &rect.pos)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
// set the positions after applying alignment so children can use them
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.x = rect.pos.x
|
|
|
|
|
constraints.y = rect.pos.y
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
is_hovered := zephr_ctx.ui.hovered_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
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
if (zephr_ctx.ui.active_element == 0) {
|
|
|
|
|
if (is_hovered && left_mouse_pressed) {
|
2024-01-20 06:39:49 +02:00
|
|
|
zephr_ctx.ui.active_element = hash
|
|
|
|
|
dragging = true
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
} else if (zephr_ctx.ui.active_element == hash) {
|
|
|
|
|
if (left_mouse_released) {
|
2024-01-20 06:39:49 +02:00
|
|
|
zephr_ctx.ui.active_element = 0
|
|
|
|
|
dragging = false
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dragging) {
|
2024-01-20 06:39:49 +02:00
|
|
|
slider_selection = clamp((zephr_ctx.mouse.pos.y - constraints.y) / constraints.height, 0, 1)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
model := identity()
|
2024-01-19 23:55:07 +02:00
|
|
|
translate(&model, Vec3{rect.pos.x, rect.pos.y, 0})
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
set_mat4f(color_chooser_shader, "model", model)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.BindVertexArray(ui_vao)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
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},
|
2024-01-20 06:39:49 +02:00
|
|
|
}
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, ui_vbo)
|
2024-01-19 23:55:07 +02:00
|
|
|
gl.BufferSubData(gl.ARRAY_BUFFER, 0, size_of(vertices), raw_data(&vertices))
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.DrawArrays(gl.TRIANGLES, 0, 6)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.BindVertexArray(0)
|
|
|
|
|
set_bool(color_chooser_shader, "isSlider", false)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
// draw the selection
|
|
|
|
|
triangle_con := DEFAULT_UI_CONSTRAINTS
|
2024-01-20 06:39:49 +02:00
|
|
|
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)
|
2024-01-19 23:55:07 +02:00
|
|
|
tri_style := UiStyle {
|
|
|
|
|
bg_color = COLOR_BLACK,
|
|
|
|
|
border_radius = 0,
|
|
|
|
|
align = .TOP_LEFT,
|
2024-01-20 06:39:49 +02:00
|
|
|
}
|
|
|
|
|
draw_triangle(&triangle_con, tri_style)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
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)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
element := UiElement{
|
|
|
|
|
id = hash,
|
|
|
|
|
rect = rect
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
append(&zephr_ctx.ui.elements, element)
|
|
|
|
|
|
|
|
|
|
return slider_selection
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@private
|
|
|
|
|
draw_color_picker_canvas :: proc(constraints: ^UiConstraints, slider_percentage: f32, align: Alignment, caller := #caller_location) -> Color {
|
|
|
|
|
@static
|
2024-01-20 06:39:49 +02:00
|
|
|
dragging := false
|
2024-01-19 23:55:07 +02:00
|
|
|
@static
|
2024-01-20 06:39:49 +02:00
|
|
|
canvas_pos := Vec2{0, 0}
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
line := caller.line
|
|
|
|
|
line_bytes := mem.byte_slice(&line, size_of(line))
|
2024-01-20 06:39:49 +02:00
|
|
|
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)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
use_shader(color_chooser_shader)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
set_bool(color_chooser_shader, "isSlider", false)
|
|
|
|
|
set_float(color_chooser_shader, "sliderPercentage", slider_percentage)
|
|
|
|
|
set_mat4f(color_chooser_shader, "projection", zephr_ctx.projection)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
rect: Rect = ---
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
apply_constraints(constraints, &rect.pos, &rect.size)
|
|
|
|
|
apply_alignment(align, constraints, rect.size, &rect.pos)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
// set the positions after applying alignment so children can use them
|
2024-01-20 06:39:49 +02:00
|
|
|
constraints.x = rect.pos.x
|
|
|
|
|
constraints.y = rect.pos.y
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
is_hovered := zephr_ctx.ui.hovered_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
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
if (zephr_ctx.ui.active_element == 0) {
|
|
|
|
|
if (is_hovered && left_mouse_pressed) {
|
2024-01-20 06:39:49 +02:00
|
|
|
zephr_ctx.ui.active_element = hash
|
|
|
|
|
dragging = true
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
} else if (zephr_ctx.ui.active_element == hash) {
|
|
|
|
|
if (left_mouse_released) {
|
2024-01-20 06:39:49 +02:00
|
|
|
zephr_ctx.ui.active_element = 0
|
|
|
|
|
dragging = false
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dragging) {
|
2024-01-20 06:39:49 +02:00
|
|
|
x := (zephr_ctx.mouse.pos.x - constraints.x) / constraints.width
|
|
|
|
|
y := (zephr_ctx.mouse.pos.y - constraints.y) / constraints.height
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
canvas_pos.x = clamp(x, 0, 1)
|
|
|
|
|
canvas_pos.y = clamp(y, 0, 1)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
model := identity()
|
|
|
|
|
translate(&model, Vec3{rect.pos.x, rect.pos.y, 0})
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
set_mat4f(color_chooser_shader, "model", model)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.BindVertexArray(ui_vao)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
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},
|
2024-01-20 06:39:49 +02:00
|
|
|
}
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, ui_vbo)
|
2024-01-19 23:55:07 +02:00
|
|
|
gl.BufferSubData(gl.ARRAY_BUFFER, 0, size_of(vertices), raw_data(&vertices))
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.DrawArrays(gl.TRIANGLES, 0, 6)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
gl.BindVertexArray(0)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
selected_color := hsv2rgb(slider_percentage * 360, canvas_pos.x, 1.0 - canvas_pos.y)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
// draw the selection
|
|
|
|
|
triangle_con := DEFAULT_UI_CONSTRAINTS
|
2024-01-20 06:39:49 +02:00
|
|
|
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)
|
2024-01-19 23:55:07 +02:00
|
|
|
tri_style := UiStyle {
|
|
|
|
|
bg_color = determine_color_contrast(selected_color),
|
|
|
|
|
border_radius = 0,
|
|
|
|
|
align = .TOP_LEFT,
|
2024-01-20 06:39:49 +02:00
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
element := UiElement{
|
|
|
|
|
id = hash,
|
|
|
|
|
rect = rect
|
|
|
|
|
}
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
append(&zephr_ctx.ui.elements, element)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
return selected_color
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@private
|
|
|
|
|
draw_color_picker_popup :: proc(picker_button_con: ^UiConstraints) {
|
2024-01-20 06:39:49 +02:00
|
|
|
id := 023467
|
|
|
|
|
id_bytes := mem.byte_slice(&id, size_of(id))
|
|
|
|
|
hash := fnv_hash32(id_bytes, size_of(id), zephr_ctx.ui.popup_parent_hash)
|
2024-01-19 23:55:07 +02:00
|
|
|
popup_con := DEFAULT_UI_CONSTRAINTS
|
2024-01-20 06:39:49 +02:00
|
|
|
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)
|
2024-01-19 23:55:07 +02:00
|
|
|
popup_style := UiStyle {
|
|
|
|
|
bg_color = mult_color(COLOR_WHITE, 0.94),
|
|
|
|
|
border_radius = popup_con.width * 0.02,
|
|
|
|
|
align = .TOP_LEFT,
|
2024-01-20 06:39:49 +02:00
|
|
|
}
|
|
|
|
|
draw_quad(&popup_con, popup_style)
|
|
|
|
|
|
|
|
|
|
rect := Rect {
|
|
|
|
|
{popup_con.x, popup_con.y},
|
|
|
|
|
{popup_con.width, popup_con.height},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
element := UiElement{
|
|
|
|
|
id = hash,
|
|
|
|
|
rect = rect
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
append(&zephr_ctx.ui.elements, element)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
color_slider_con := DEFAULT_UI_CONSTRAINTS
|
2024-01-20 06:39:49 +02:00
|
|
|
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)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
button_con := DEFAULT_UI_CONSTRAINTS
|
2024-01-20 06:39:49 +02:00
|
|
|
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)
|
2024-01-19 23:55:07 +02:00
|
|
|
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)) {
|
2024-01-20 06:39:49 +02:00
|
|
|
zephr_ctx.ui.popup_open = false
|
|
|
|
|
zephr_ctx.ui.popup_parent_hash = 0
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
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
|
2024-01-19 23:55:07 +02:00
|
|
|
if (draw_button(&button_con, "Apply", button_style, .ACTIVE)) {
|
2024-01-20 06:39:49 +02:00
|
|
|
zephr_ctx.ui.popup_revert_color^ = selected_color
|
|
|
|
|
zephr_ctx.ui.popup_open = false
|
|
|
|
|
zephr_ctx.ui.popup_parent_hash = 0
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
zephr_ctx.ui.popup_rect = rect
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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))
|
2024-01-20 06:39:49 +02:00
|
|
|
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)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
rect: Rect = ---
|
2024-01-20 06:39:49 +02:00
|
|
|
display_color := color^
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
con := constraints^
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
apply_constraints(&con, &rect.pos, &rect.size)
|
|
|
|
|
apply_alignment(align, &con, rect.size, &rect.pos)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
is_held := zephr_ctx.ui.active_element == hash
|
2024-01-20 06:39:49 +02:00
|
|
|
is_hovered := zephr_ctx.ui.hovered_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
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
if (zephr_ctx.ui.active_element == 0) {
|
|
|
|
|
if (is_hovered && left_mouse_pressed) {
|
2024-01-20 06:39:49 +02:00
|
|
|
zephr_ctx.ui.active_element = hash
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
} else if (zephr_ctx.ui.active_element == hash) {
|
|
|
|
|
if (left_mouse_released) {
|
2024-01-20 06:39:49 +02:00
|
|
|
zephr_ctx.ui.active_element = 0
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
if (is_hovered) {
|
2024-01-20 06:39:49 +02:00
|
|
|
clicked = true
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_hovered && state == .ACTIVE) {
|
2024-01-20 06:39:49 +02:00
|
|
|
set_cursor(.HAND)
|
|
|
|
|
con.scale.y *= 1.08
|
|
|
|
|
con.scale.x *= 1.08
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
if (is_held) {
|
2024-01-20 06:39:49 +02:00
|
|
|
display_color = mult_color(display_color, 0.8)
|
2024-01-19 23:55:07 +02:00
|
|
|
} else {
|
2024-01-20 06:39:49 +02:00
|
|
|
display_color = mult_color(display_color, 0.9)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
} else if (state == .DISABLED) {
|
|
|
|
|
if (is_hovered) {
|
2024-01-20 06:39:49 +02:00
|
|
|
set_cursor(.DISABLED)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
display_color.a = 100
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
style := UiStyle {
|
|
|
|
|
bg_color = display_color,
|
|
|
|
|
border_radius = con.width * 0.08,
|
|
|
|
|
align = align,
|
2024-01-20 06:39:49 +02:00
|
|
|
}
|
|
|
|
|
draw_quad(&con, style)
|
2024-01-19 23:55:07 +02:00
|
|
|
|
|
|
|
|
if (clicked && state == .ACTIVE) {
|
2024-01-20 06:39:49 +02:00
|
|
|
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
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (zephr_ctx.ui.popup_parent_hash == hash) {
|
2024-01-20 06:39:49 +02:00
|
|
|
zephr_ctx.ui.popup_open = true
|
|
|
|
|
zephr_ctx.ui.popup_parent_constraints = con
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
element := UiElement{
|
|
|
|
|
rect = rect,
|
|
|
|
|
id = hash
|
|
|
|
|
}
|
2024-01-19 23:55:07 +02:00
|
|
|
|
2024-01-20 06:39:49 +02:00
|
|
|
append(&zephr_ctx.ui.elements, element)
|
2024-01-19 23:55:07 +02:00
|
|
|
}
|